Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ jobs:
test:
- path: 'components/adc/example'
target: esp32
- path: 'components/adc/example'
target: esp32s3
- path: 'components/adc/example'
target: esp32p4
- path: 'components/adc/example'
target: esp32c6
- path: 'components/adrc/example'
target: esp32
- path: 'components/ads1x15/example'
Expand Down
27 changes: 26 additions & 1 deletion components/adc/example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,34 @@ ADC hardware, supporting DMA and filtering as well.
## How to use example

You can build and run this example on any esp dev board, such as the QtPy ESP32
series, the TinyPICO / TinyS3 boards, the WROVER-DevKit, and the ESP32-S3-BOX.
series, the TinyPICO / TinyS3 boards, the WROVER-DevKit, and the M5Stack Tab5.
Make sure to set the idf target when building the example.

### Configure

Select which ADC unit / channels the example reads via the `ADC Example
Configuration` menu:

```
idf.py menuconfig
```

The `Hardware` option has selections for specific boards which set the ADC
unit and channels to pins that are usable (exposed) on that board:

| Hardware | Unit | Channels | Pins |
|----------|------|----------|------|
| Adafruit QtPy ESP32 Pico | 1 | 4, 5 | GPIO32 (`TX`), GPIO33 (`SCL`) |
| Adafruit QtPy ESP32-S3 | 1 | 7, 8 | GPIO8 (`A3`), GPIO9 (`A2`) |
| M5Stack Tab5 (ESP32-P4) | 1 | 0, 1 | GPIO16 / GPIO17 (expansion header) |
| Custom | any | any | your choice |
Comment thread
finger563 marked this conversation as resolved.

With `Custom`, set the ADC unit and the two channels yourself. Note that the
continuous (DMA) portion of the example requires a DMA-capable unit; on
ESP32 / ESP32-C3 / ESP32-S3 that is only unit 1, and on ESP32-P4 ADC2
continuous conversions currently produce all-zero samples with esp-idf
(oneshot on ADC2, e.g. the Tab5's Grove pins GPIO53/54, works fine).

### Build and Flash

Build the project and flash it to the board, then run monitor tool to view serial output:
Expand Down
114 changes: 114 additions & 0 deletions components/adc/example/main/Kconfig.projbuild
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
menu "ADC Example Configuration"

choice EXAMPLE_HARDWARE
prompt "Hardware"
default EXAMPLE_HARDWARE_QTPY_ESP32 if IDF_TARGET_ESP32
default EXAMPLE_HARDWARE_QTPY_ESP32S3 if IDF_TARGET_ESP32S3
default EXAMPLE_HARDWARE_M5STACK_TAB5 if IDF_TARGET_ESP32P4
default EXAMPLE_HARDWARE_CUSTOM
help
Which hardware to run the example on. The board selections
configure the ADC unit and channels to pins which are usable
(exposed) on that board; select custom to choose the ADC unit and
channels yourself.

config EXAMPLE_HARDWARE_QTPY_ESP32
depends on IDF_TARGET_ESP32
bool "Adafruit QtPy ESP32 Pico"
help
Uses ADC unit 1, channels 4 and 5: the pins labeled 'TX'
(GPIO32) and 'SCL' (GPIO33), the only ADC1-capable pins
exposed on the board.

config EXAMPLE_HARDWARE_QTPY_ESP32S3
depends on IDF_TARGET_ESP32S3
bool "Adafruit QtPy ESP32-S3"
help
Uses ADC unit 1, channels 7 and 8: the pins labeled 'A3'
(GPIO8) and 'A2' (GPIO9).

config EXAMPLE_HARDWARE_M5STACK_TAB5
depends on IDF_TARGET_ESP32P4
bool "M5Stack Tab5"
help
Uses ADC unit 1, channels 0 and 1: GPIO16 and GPIO17, which
are exposed on the Tab5's expansion header.

NOTE: the Grove connector's signal pins (GPIO53/54) are ADC
unit 2 channels 4/5 and work with the oneshot ADC, but ADC2
continuous (DMA) conversions on the ESP32-P4 currently
produce all-zero samples with esp-idf, so this example uses
ADC1 pins instead.

config EXAMPLE_HARDWARE_CUSTOM
bool "Custom"
help
Select the ADC unit and channels yourself.
endchoice

config EXAMPLE_ADC_UNIT
int "ADC unit (1 or 2)" if EXAMPLE_HARDWARE_CUSTOM
range 1 2
default 1
help
The ADC unit both channels are read from.

NOTE: the continuous (DMA) part of the example requires a
DMA-capable unit; on ESP32, ESP32-C3, and ESP32-S3 that is only
unit 1. On ESP32-P4, ADC2 continuous conversions currently
produce all-zero samples with esp-idf, so prefer unit 1 there as
well.

config EXAMPLE_ADC_CHANNEL_1
int "First ADC channel" if EXAMPLE_HARDWARE_CUSTOM
range 0 9
default 4 if EXAMPLE_HARDWARE_QTPY_ESP32
default 7 if EXAMPLE_HARDWARE_QTPY_ESP32S3
default 0 if EXAMPLE_HARDWARE_M5STACK_TAB5
default 2
help
The first ADC channel to read. Which GPIO a channel maps to is
chip specific; see the ADC section of your chip's technical
reference manual (e.g. ADC1 channels 0-7 are GPIO 36-39/32-35 on
ESP32, GPIO 1-10 on ESP32-S3, GPIO 16-23 on ESP32-P4).

config EXAMPLE_ADC_CHANNEL_2
int "Second ADC channel" if EXAMPLE_HARDWARE_CUSTOM
range 0 9
default 5 if EXAMPLE_HARDWARE_QTPY_ESP32
default 8 if EXAMPLE_HARDWARE_QTPY_ESP32S3
default 1 if EXAMPLE_HARDWARE_M5STACK_TAB5
default 3
help
The second ADC channel to read. See EXAMPLE_ADC_CHANNEL_1 for the
channel-to-GPIO mapping.

config EXAMPLE_ADC_SAMPLE_RATE_HZ
int "Continuous ADC Sample Rate (Hz)"
range 0 1000000
default 20000 if EXAMPLE_HARDWARE_QTPY_ESP32
default 20000 if EXAMPLE_HARDWARE_QTPY_ESP32S3
default 10000 if EXAMPLE_HARDWARE_M5STACK_TAB5
default 20000
help
The sample rate for the continuous ADC readings. The example
uses a DMA buffer to read samples from the ADC. The example prints the average of each
channel's samples every second, so if you set the sample rate to
1000 Hz, it will print the average of 1000 samples per channel
every second.

config EXAMPLE_ADC_WINDOW_SIZE_BYTES
int "Continuous ADC Window Size (Bytes)"
range 0 65536
default 1024 if EXAMPLE_HARDWARE_QTPY_ESP32
default 1024 if EXAMPLE_HARDWARE_QTPY_ESP32S3
default 256 if EXAMPLE_HARDWARE_M5STACK_TAB5
default 1024
help
The size of the DMA buffer used for continuous ADC readings. The
example prints the average of each channel's samples every second,
so if you set the sample rate to 1000 Hz and the window size to
1000 bytes, it will print the average of 1000 samples per channel
every second.

endmenu
143 changes: 85 additions & 58 deletions components/adc/example/main/adc_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,72 +7,48 @@

using namespace std::chrono_literals;

// The ADC unit and channels come from the "ADC Example Configuration" menu
// (menuconfig), which has selections for specific boards (setting them to
// pins which are usable on that board) as well as a custom option. Which GPIO
// a channel maps to is chip specific.
static constexpr adc_unit_t EXAMPLE_ADC_UNIT =
(CONFIG_EXAMPLE_ADC_UNIT == 1) ? ADC_UNIT_1 : ADC_UNIT_2;
static constexpr adc_channel_t EXAMPLE_ADC_CHANNEL_1 =
static_cast<adc_channel_t>(CONFIG_EXAMPLE_ADC_CHANNEL_1);
static constexpr adc_channel_t EXAMPLE_ADC_CHANNEL_2 =
static_cast<adc_channel_t>(CONFIG_EXAMPLE_ADC_CHANNEL_2);

extern "C" void app_main(void) {
espp::Logger logger({.tag = "Adc Example", .level = espp::Logger::Verbosity::INFO});
size_t num_seconds_to_run = 5;

{
logger.info("Reading oneshot adc for {} seconds", num_seconds_to_run);
//! [oneshot adc example]
std::vector<espp::AdcConfig> channels{
{.unit = ADC_UNIT_1, .channel = ADC_CHANNEL_6, .attenuation = ADC_ATTEN_DB_12},
{.unit = ADC_UNIT_1, .channel = ADC_CHANNEL_7, .attenuation = ADC_ATTEN_DB_12}};
espp::OneshotAdc adc({
.unit = ADC_UNIT_1,
.channels = channels,
});
auto task_fn = [&adc, &channels](std::mutex &m, std::condition_variable &cv) {
static bool use_individual_functions = false;
if (use_individual_functions) {
// this iteration, we'll use the read_mv function for each channel
for (auto &conf : channels) {
auto maybe_mv = adc.read_mv(conf);
if (maybe_mv.has_value()) {
fmt::print("{}: {} mV\n", conf, maybe_mv.value());
} else {
fmt::print("{}: no value!\n", conf);
}
}
} else {
// this iteration, we'll use the read_all_mv function to read all
// configured channels
auto voltages = adc.read_all_mv();
for (const auto &mv : voltages) {
fmt::print("{} mV\n", mv);
}
}
use_individual_functions = !use_individual_functions;
// NOTE: sleeping in this way allows the sleep to exit early when the
// task is being stopped / destroyed
{
std::unique_lock<std::mutex> lk(m);
cv.wait_for(lk, 500ms);
}
// don't want to stop the task
return false;
};
auto task = espp::Task({.callback = task_fn,
.task_config = {.name = "Oneshot ADC"},
.log_level = espp::Logger::Verbosity::INFO});
task.start();
//! [oneshot adc example]
std::this_thread::sleep_for(num_seconds_to_run * 1s);
}
logger.info("Using ADC unit {} channels {} and {}", CONFIG_EXAMPLE_ADC_UNIT,
(int)EXAMPLE_ADC_CHANNEL_1, (int)EXAMPLE_ADC_CHANNEL_2);

// NOTE: the continuous section runs first: on ESP32-P4, initializing and
// then deleting the oneshot ADC driver before using the continuous (DMA)
// driver currently causes the continuous conversions to produce all-zero
// samples (esp-idf issue; verified on hardware with IDF v6.0.1)
{
logger.info("Reading continuous adc for {} seconds", num_seconds_to_run);
//! [continuous adc example]
std::vector<espp::AdcConfig> channels{
{.unit = ADC_UNIT_1, .channel = ADC_CHANNEL_6, .attenuation = ADC_ATTEN_DB_12},
{.unit = ADC_UNIT_1, .channel = ADC_CHANNEL_7, .attenuation = ADC_ATTEN_DB_12}};
// this initailizes the DMA and filter task for the continuous adc
espp::ContinuousAdc adc(
{.sample_rate_hz = 20 * 1000,
.channels = channels,
.convert_mode =
ADC_CONV_SINGLE_UNIT_1, // or BOTH_UNIT, ALTER_UNIT, SINGLE_UNIT_1, SINGLE_UNIT_2
.window_size_bytes = 1024,
.log_level = espp::Logger::Verbosity::WARN});
// NOTE: the unit must support continuous (DMA) mode; on ESP32 / C3 / S3
// that is only unit 1
std::vector<espp::AdcConfig> channels{{.unit = EXAMPLE_ADC_UNIT,
.channel = EXAMPLE_ADC_CHANNEL_1,
.attenuation = ADC_ATTEN_DB_12},
{.unit = EXAMPLE_ADC_UNIT,
.channel = EXAMPLE_ADC_CHANNEL_2,
.attenuation = ADC_ATTEN_DB_12}};
// this initializes the DMA and filter task for the continuous adc. The
// conversion mode is derived automatically from the channel units; you
// can also set it explicitly with e.g. .convert_mode =
// ADC_CONV_SINGLE_UNIT_1 (chip-dependent: BOTH_UNIT, ALTER_UNIT,
// SINGLE_UNIT_1, SINGLE_UNIT_2)
espp::ContinuousAdc adc({.sample_rate_hz = CONFIG_EXAMPLE_ADC_SAMPLE_RATE_HZ,
.channels = channels,
.window_size_bytes = CONFIG_EXAMPLE_ADC_WINDOW_SIZE_BYTES,
.log_level = espp::Logger::Verbosity::WARN});
adc.start();
auto task_fn = [&adc, &channels](std::mutex &m, std::condition_variable &cv) {
for (auto &conf : channels) {
Expand Down Expand Up @@ -115,6 +91,57 @@ extern "C" void app_main(void) {
std::this_thread::sleep_for(num_seconds_to_run * 1s);
}

{
logger.info("Reading oneshot adc for {} seconds", num_seconds_to_run);
//! [oneshot adc example]
std::vector<espp::AdcConfig> channels{{.unit = EXAMPLE_ADC_UNIT,
.channel = EXAMPLE_ADC_CHANNEL_1,
.attenuation = ADC_ATTEN_DB_12},
{.unit = EXAMPLE_ADC_UNIT,
.channel = EXAMPLE_ADC_CHANNEL_2,
.attenuation = ADC_ATTEN_DB_12}};
espp::OneshotAdc adc({
.unit = EXAMPLE_ADC_UNIT,
.channels = channels,
});
auto task_fn = [&adc, &channels](std::mutex &m, std::condition_variable &cv) {
static bool use_individual_functions = false;
if (use_individual_functions) {
// this iteration, we'll use the read_mv function for each channel
for (auto &conf : channels) {
auto maybe_mv = adc.read_mv(conf);
if (maybe_mv.has_value()) {
fmt::print("{}: {} mV\n", conf, maybe_mv.value());
} else {
fmt::print("{}: no value!\n", conf);
}
}
} else {
// this iteration, we'll use the read_all_mv function to read all
// configured channels
auto voltages = adc.read_all_mv();
for (const auto &mv : voltages) {
fmt::print("{} mV\n", mv);
}
}
use_individual_functions = !use_individual_functions;
// NOTE: sleeping in this way allows the sleep to exit early when the
// task is being stopped / destroyed
{
std::unique_lock<std::mutex> lk(m);
cv.wait_for(lk, 500ms);
}
// don't want to stop the task
return false;
};
auto task = espp::Task({.callback = task_fn,
.task_config = {.name = "Oneshot ADC"},
.log_level = espp::Logger::Verbosity::INFO});
task.start();
//! [oneshot adc example]
std::this_thread::sleep_for(num_seconds_to_run * 1s);
}

logger.info("complete!");

while (true) {
Expand Down
6 changes: 4 additions & 2 deletions components/adc/include/adc_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ struct AdcConfig {
attenuation; /**< The attenuation associated with this channel, e.g. ADC_ATTEN_DB_12. */
};

static bool operator!=(const AdcConfig &lhs, const AdcConfig &rhs) {
[[maybe_unused]] static bool operator!=(const AdcConfig &lhs, const AdcConfig &rhs) {
return lhs.unit != rhs.unit || lhs.channel != rhs.channel || lhs.attenuation != rhs.attenuation;
}

static bool operator==(const AdcConfig &lhs, const AdcConfig &rhs) { return !(lhs != rhs); }
[[maybe_unused]] static bool operator==(const AdcConfig &lhs, const AdcConfig &rhs) {
return !(lhs != rhs);
}
} // namespace espp

// for libfmt printing of adc_unit_t
Expand Down
Loading
Loading