refactor(dac): remove adc read back in dac example, prepare for s31

This commit is contained in:
Hu Rui
2026-08-25 17:53:39 +08:00
parent 13e18a6a43
commit 7bb7fed5ec
32 changed files with 481 additions and 6600 deletions

View File

@@ -602,7 +602,7 @@ clean_up:
//////////////////////////////////// Cyclic writing //////////////////////////////////// //////////////////////////////////// Cyclic writing ////////////////////////////////////
esp_err_t dac_continuous_write_cyclically(dac_continuous_handle_t handle, uint8_t *buf, size_t buf_size, size_t *bytes_loaded) esp_err_t dac_continuous_write_cyclically(dac_continuous_handle_t handle, const uint8_t *buf, size_t buf_size, size_t *bytes_loaded)
{ {
DAC_NULL_POINTER_CHECK(handle); DAC_NULL_POINTER_CHECK(handle);
DAC_NULL_POINTER_CHECK(buf); DAC_NULL_POINTER_CHECK(buf);
@@ -687,7 +687,7 @@ esp_err_t dac_continuous_stop_cyclically(dac_continuous_handle_t handle)
//////////////////////////////////// Synchronous writing //////////////////////////////////// //////////////////////////////////// Synchronous writing ////////////////////////////////////
esp_err_t dac_continuous_write(dac_continuous_handle_t handle, uint8_t *buf, size_t buf_size, size_t *bytes_loaded, int timeout_ms) esp_err_t dac_continuous_write(dac_continuous_handle_t handle, const uint8_t *buf, size_t buf_size, size_t *bytes_loaded, int timeout_ms)
{ {
DAC_NULL_POINTER_CHECK(handle); DAC_NULL_POINTER_CHECK(handle);
DAC_NULL_POINTER_CHECK(buf); DAC_NULL_POINTER_CHECK(buf);
@@ -822,3 +822,12 @@ static esp_err_t s_dac_continuous_stop_sync(dac_continuous_handle_t handle)
return ESP_OK; return ESP_OK;
} }
uint8_t dac_continuous_get_bitwidth(dac_continuous_handle_t handle)
{
if (!handle) {
return 0;
}
return SOC_DAC_RESOLUTION;
}

View File

@@ -144,3 +144,12 @@ esp_err_t dac_cosine_stop(dac_cosine_handle_t handle)
return ESP_OK; return ESP_OK;
} }
uint8_t dac_cosine_get_bitwidth(dac_cosine_handle_t handle)
{
if (!handle) {
return 0;
}
return SOC_DAC_RESOLUTION;
}

View File

@@ -69,3 +69,12 @@ esp_err_t dac_oneshot_output_voltage(dac_oneshot_handle_t handle, uint8_t digi_v
return ESP_OK; return ESP_OK;
} }
uint8_t dac_oneshot_get_bitwidth(dac_oneshot_handle_t handle)
{
if (!handle) {
return 0;
}
return SOC_DAC_RESOLUTION;
}

View File

@@ -153,7 +153,7 @@ esp_err_t dac_continuous_disable(dac_continuous_handle_t handle);
* - ESP_ERR_TIMEOUT Waiting for semaphore or message queue timeout * - ESP_ERR_TIMEOUT Waiting for semaphore or message queue timeout
* - ESP_OK Success to output the acyclic DAC data * - ESP_OK Success to output the acyclic DAC data
*/ */
esp_err_t dac_continuous_write(dac_continuous_handle_t handle, uint8_t *buf, size_t buf_size, size_t *bytes_loaded, int timeout_ms); esp_err_t dac_continuous_write(dac_continuous_handle_t handle, const uint8_t *buf, size_t buf_size, size_t *bytes_loaded, int timeout_ms);
/** /**
* @brief Write DAC continuous data cyclically * @brief Write DAC continuous data cyclically
@@ -176,7 +176,7 @@ esp_err_t dac_continuous_write(dac_continuous_handle_t handle, uint8_t *buf, siz
* - ESP_ERR_INVALID_STATE The DAC continuous mode has not been enabled yet * - ESP_ERR_INVALID_STATE The DAC continuous mode has not been enabled yet
* - ESP_OK Success to output the cyclic DAC data * - ESP_OK Success to output the cyclic DAC data
*/ */
esp_err_t dac_continuous_write_cyclically(dac_continuous_handle_t handle, uint8_t *buf, size_t buf_size, size_t *bytes_loaded); esp_err_t dac_continuous_write_cyclically(dac_continuous_handle_t handle, const uint8_t *buf, size_t buf_size, size_t *bytes_loaded);
/** /**
* @brief Stop the cyclical conversion triggered by 'dac_continuous_write_cyclically' * @brief Stop the cyclical conversion triggered by 'dac_continuous_write_cyclically'
@@ -257,6 +257,14 @@ esp_err_t dac_continuous_write_asynchronously(dac_continuous_handle_t handle,
size_t data_len, size_t data_len,
size_t *bytes_loaded); size_t *bytes_loaded);
/**
* @brief Get the DAC code bit width of the continuous channel group
*
* @param[in] handle The DAC continuous channel handle
* @return The DAC code bit width, 0 if the input parameter is invalid
*/
uint8_t dac_continuous_get_bitwidth(dac_continuous_handle_t handle);
#endif // SOC_DAC_SUPPORTED #endif // SOC_DAC_SUPPORTED
#ifdef __cplusplus #ifdef __cplusplus

View File

@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2019-2026 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -91,6 +91,14 @@ esp_err_t dac_cosine_start(dac_cosine_handle_t handle);
*/ */
esp_err_t dac_cosine_stop(dac_cosine_handle_t handle); esp_err_t dac_cosine_stop(dac_cosine_handle_t handle);
/**
* @brief Get the DAC code bit width of a cosine wave channel
*
* @param[in] handle The DAC cosine wave channel handle
* @return The DAC code bit width, 0 if the input parameter is invalid
*/
uint8_t dac_cosine_get_bitwidth(dac_cosine_handle_t handle);
#endif // SOC_DAC_SUPPORTED #endif // SOC_DAC_SUPPORTED
#ifdef __cplusplus #ifdef __cplusplus

View File

@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2019-2026 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -63,6 +63,14 @@ esp_err_t dac_oneshot_del_channel(dac_oneshot_handle_t handle);
*/ */
esp_err_t dac_oneshot_output_voltage(dac_oneshot_handle_t handle, uint8_t digi_value); esp_err_t dac_oneshot_output_voltage(dac_oneshot_handle_t handle, uint8_t digi_value);
/**
* @brief Get the DAC code bit width of a oneshot channel
*
* @param[in] handle The DAC oneshot channel handle
* @return The DAC code bit width, 0 if the input parameter is invalid
*/
uint8_t dac_oneshot_get_bitwidth(dac_oneshot_handle_t handle);
#endif // SOC_DAC_SUPPORTED #endif // SOC_DAC_SUPPORTED
#ifdef __cplusplus #ifdef __cplusplus

View File

@@ -129,11 +129,11 @@ The table below provides more information on pin usage, and please note the comm
- -
- -
* - GPIO25 * - GPIO25
- ADC2_CH8 - ADC2_CH8, DAC0
- RTC_GPIO6 - RTC_GPIO6
- -
* - GPIO26 * - GPIO26
- ADC2_CH9 - ADC2_CH9, DAC1
- RTC_GPIO7 - RTC_GPIO7
- -
* - GPIO27 * - GPIO27

View File

@@ -91,11 +91,11 @@ The table below provides more information on pin usage, and please note the comm
- RTC_GPIO16 - RTC_GPIO16
- -
* - GPIO17 * - GPIO17
- ADC2_CH6 - ADC2_CH6, DAC0
- RTC_GPIO17 - RTC_GPIO17
- -
* - GPIO18 * - GPIO18
- ADC2_CH7 - ADC2_CH7, DAC1
- RTC_GPIO18 - RTC_GPIO18
- -
* - GPIO19 * - GPIO19

View File

@@ -129,11 +129,11 @@
- -
- -
* - GPIO25 * - GPIO25
- ADC2_CH8 - ADC2_CH8, DAC0
- RTC_GPIO6 - RTC_GPIO6
- -
* - GPIO26 * - GPIO26
- ADC2_CH9 - ADC2_CH9, DAC1
- RTC_GPIO7 - RTC_GPIO7
- -
* - GPIO27 * - GPIO27

View File

@@ -91,11 +91,11 @@
- RTC_GPIO16 - RTC_GPIO16
- -
* - GPIO17 * - GPIO17
- ADC2_CH6 - ADC2_CH6, DAC0
- RTC_GPIO17 - RTC_GPIO17
- -
* - GPIO18 * - GPIO18
- ADC2_CH7 - ADC2_CH7, DAC1
- RTC_GPIO18 - RTC_GPIO18
- -
* - GPIO19 * - GPIO19

View File

@@ -1,7 +1,7 @@
| Supported Targets | ESP32 | ESP32-S2 | | Supported Targets | ESP32 | ESP32-S2 |
| ----------------- | ----- | -------- | | ----------------- | ----- | -------- |
# DAC Constant Example # DAC Continuous Audio Example
(See the README.md file in the upper level 'examples' directory for more information about examples.) (See the README.md file in the upper level 'examples' directory for more information about examples.)
@@ -14,13 +14,23 @@ This example shows how to play a piece of audio by DAC driver.
### Hardware Required ### Hardware Required
* A development board with ESP32 or ESP32-S2 SoC * A development board with ESP32 or ESP32-S2 SoC
- Note that some ESP32-S2 DevKits have LED on it which is connected to GPIO18 (same pin as DAC channel2), so the output voltage of DAC channel 1 can't go down due the this LED. * DAC channel to GPIO mapping: see [GPIO Summary](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/gpio.html#gpio-summary) (switch the chip target on the documentation page if needed)
* Note that some ESP32-S2 DevKits have an LED on the DAC1 pin, so that channel's output may not go fully low
* An Audio Power Amplifier like `NS4150` * An Audio Power Amplifier like `NS4150`
* A speaker or earphone to play the audio * A speaker or earphone to play the audio
### Configure the Project ### Configure the Project
This example uses the audio that stored in a buffer, which is put in `audio_example_file.h`. You can also create your own audio buffer by the python script `generate_audio_file.py`. The audio table and sample rate are extracted at build time from a WAV file under `tools/` and written into a generated header in the build directory. In menuconfig, under ``Example Configuration``:
* ``WAV file name``: file under `tools/` (default `hi_idf_audio.wav`)
* ``DAC audio table bit width``: Please select based on the DAC channel configuration
You can also run the converter by hand to inspect the table:
```
python tools/generate_audio_file.py --bitwidth 8 -o audio_example_file.h tools/hi_idf_audio.wav
```
### Build and Flash ### Build and Flash
@@ -41,12 +51,12 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui
You can see the following logs on the monitor: You can see the following logs on the monitor:
``` ```
I (277) dac audio: DAC audio example start I (277) dac_audio: DAC audio example start
I (277) dac audio: -------------------------------------- I (277) dac_audio: --------------------------------------
I (287) dac audio: DAC initialized success, DAC DMA is ready I (287) dac_audio: DAC initialized success, DAC DMA is ready
I (297) dac audio: Audio size 79512 bytes, played at frequency 16000 Hz I (297) dac_audio: Audio size 95824 bytes, played at frequency 48000 Hz synchronously
I (5137) dac audio: Audio size 79512 bytes, played at frequency 16000 Hz Play count: 1
I (9967) dac audio: Audio size 79512 bytes, played at frequency 16000 Hz Play count: 2
... ...
``` ```

View File

@@ -1,3 +1,29 @@
idf_component_register(SRCS "dac_audio_example_main.c" idf_component_register(SRCS "dac_audio_example_main.c"
INCLUDE_DIRS "." INCLUDE_DIRS "."
PRIV_REQUIRES esp_driver_dac) PRIV_REQUIRES esp_driver_dac)
# Generate the audio table header at build time
set(audio_header ${CMAKE_CURRENT_BINARY_DIR}/audio_example_file.h)
set(audio_gen_script ${CMAKE_CURRENT_SOURCE_DIR}/../tools/generate_audio_file.py)
set(audio_wav ${CMAKE_CURRENT_SOURCE_DIR}/../tools/${CONFIG_EXAMPLE_AUDIO_WAV_FILE})
idf_build_get_property(python PYTHON)
idf_build_get_property(project_dir PROJECT_DIR)
add_custom_command(
OUTPUT ${audio_header}
COMMAND ${python} ${audio_gen_script}
--bitwidth ${CONFIG_EXAMPLE_DAC_AUDIO_BITWIDTH}
-o ${audio_header}
${audio_wav}
DEPENDS ${audio_gen_script} ${audio_wav} ${project_dir}/sdkconfig
COMMENT "Generating DAC audio table from ${CONFIG_EXAMPLE_AUDIO_WAV_FILE}"
VERBATIM
)
add_custom_target(dac_audio_header DEPENDS ${audio_header})
add_dependencies(${COMPONENT_LIB} dac_audio_header)
set_source_files_properties(dac_audio_example_main.c PROPERTIES OBJECT_DEPENDS ${audio_header})
target_include_directories(${COMPONENT_LIB} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
APPEND PROPERTY ADDITIONAL_CLEAN_FILES ${audio_header})

View File

@@ -12,10 +12,35 @@ menu "Example Configuration"
bool "Asynchronous transmitting" bool "Asynchronous transmitting"
endchoice endchoice
config EXAMPLE_AUDIO_SAMPLE_RATE config EXAMPLE_AUDIO_WAV_FILE
int "The audio sample rate (Unit: Hz)" string "WAV file name"
default 48000 default "hi_idf_audio.wav"
help help
The audio sample rate WAV file under the example tools/ directory. It is converted to a DAC
code table at build time.
choice EXAMPLE_DAC_AUDIO_BITWIDTH_SEL
prompt "DAC audio table bit width"
default EXAMPLE_DAC_AUDIO_BITWIDTH_8 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2
default EXAMPLE_DAC_AUDIO_BITWIDTH_12 if IDF_TARGET_ESP32S31
help
Bit width of the generated DAC codes. Must match the DAC channel configuration.
config EXAMPLE_DAC_AUDIO_BITWIDTH_8
bool "8 bits"
depends on IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2
config EXAMPLE_DAC_AUDIO_BITWIDTH_10
bool "10 bits"
depends on IDF_TARGET_ESP32S31
config EXAMPLE_DAC_AUDIO_BITWIDTH_12
bool "12 bits"
depends on IDF_TARGET_ESP32S31
endchoice
config EXAMPLE_DAC_AUDIO_BITWIDTH
int
default 8 if EXAMPLE_DAC_AUDIO_BITWIDTH_8
default 10 if EXAMPLE_DAC_AUDIO_BITWIDTH_10
default 12 if EXAMPLE_DAC_AUDIO_BITWIDTH_12
endmenu endmenu

View File

@@ -11,15 +11,15 @@
#include "freertos/queue.h" #include "freertos/queue.h"
#include "driver/dac_continuous.h" #include "driver/dac_continuous.h"
#include "esp_check.h" #include "esp_check.h"
#include "audio_example_file.h" #include "audio_example_file.h" // This header file is automatically generated during the build process by tools/generate_audio_file.py
static const char *TAG = "dac_audio"; static const char *TAG = "dac_audio";
#if CONFIG_EXAMPLE_DAC_WRITE_ASYNC #if CONFIG_EXAMPLE_DAC_WRITE_ASYNC
static bool IRAM_ATTR dac_on_convert_done_callback(dac_continuous_handle_t handle, const dac_event_data_t *event, void *user_data) static bool IRAM_ATTR example_on_convert_done_callback(dac_continuous_handle_t handle, const dac_event_data_t *event, void *user_data)
{ {
QueueHandle_t que = (QueueHandle_t)user_data; QueueHandle_t que = (QueueHandle_t)user_data;
BaseType_t need_awoke; BaseType_t need_awoke = pdFALSE;
/* When the queue is full, drop the oldest item */ /* When the queue is full, drop the oldest item */
if (xQueueIsQueueFullFromISR(que)) { if (xQueueIsQueueFullFromISR(que)) {
dac_event_data_t dummy; dac_event_data_t dummy;
@@ -30,9 +30,10 @@ static bool IRAM_ATTR dac_on_convert_done_callback(dac_continuous_handle_t hand
return need_awoke; return need_awoke;
} }
static void dac_write_data_asynchronously(dac_continuous_handle_t handle, QueueHandle_t que, uint8_t *data, size_t data_size) static void example_write_data_asynchronously(dac_continuous_handle_t handle, QueueHandle_t que)
{ {
ESP_LOGI(TAG, "Audio size %d bytes, played at frequency %d Hz asynchronously", data_size, CONFIG_EXAMPLE_AUDIO_SAMPLE_RATE); size_t data_size = sizeof(audio_table);
ESP_LOGI(TAG, "Audio size %d bytes, played at frequency %d Hz asynchronously", data_size, AUDIO_SAMPLE_RATE_HZ);
uint32_t cnt = 1; uint32_t cnt = 1;
while (1) { while (1) {
printf("Play count: %"PRIu32"\n", cnt++); printf("Play count: %"PRIu32"\n", cnt++);
@@ -43,7 +44,7 @@ static void dac_write_data_asynchronously(dac_continuous_handle_t handle, QueueH
xQueueReceive(que, &evt_data, portMAX_DELAY); xQueueReceive(que, &evt_data, portMAX_DELAY);
size_t loaded_bytes = 0; size_t loaded_bytes = 0;
ESP_ERROR_CHECK(dac_continuous_write_asynchronously(handle, evt_data.buf, evt_data.buf_size, ESP_ERROR_CHECK(dac_continuous_write_asynchronously(handle, evt_data.buf, evt_data.buf_size,
data + byte_written, data_size - byte_written, &loaded_bytes)); audio_table + byte_written, data_size - byte_written, &loaded_bytes));
byte_written += loaded_bytes; byte_written += loaded_bytes;
} }
/* Clear the legacy data in DMA, clear times equal to the 'dac_continuous_config_t::desc_num' */ /* Clear the legacy data in DMA, clear times equal to the 'dac_continuous_config_t::desc_num' */
@@ -60,13 +61,14 @@ static void dac_write_data_asynchronously(dac_continuous_handle_t handle, QueueH
} }
} }
#else #else
static void dac_write_data_synchronously(dac_continuous_handle_t handle, uint8_t *data, size_t data_size) static void example_write_data_synchronously(dac_continuous_handle_t handle)
{ {
ESP_LOGI(TAG, "Audio size %d bytes, played at frequency %d Hz synchronously", data_size, CONFIG_EXAMPLE_AUDIO_SAMPLE_RATE); size_t data_size = sizeof(audio_table);
ESP_LOGI(TAG, "Audio size %d bytes, played at frequency %d Hz synchronously", data_size, AUDIO_SAMPLE_RATE_HZ);
uint32_t cnt = 1; uint32_t cnt = 1;
while (1) { while (1) {
printf("Play count: %"PRIu32"\n", cnt++); printf("Play count: %"PRIu32"\n", cnt++);
ESP_ERROR_CHECK(dac_continuous_write(handle, data, data_size, NULL, -1)); ESP_ERROR_CHECK(dac_continuous_write(handle, audio_table, data_size, NULL, -1));
vTaskDelay(pdMS_TO_TICKS(1000)); vTaskDelay(pdMS_TO_TICKS(1000));
} }
} }
@@ -77,13 +79,13 @@ void app_main(void)
ESP_LOGI(TAG, "DAC audio example start"); ESP_LOGI(TAG, "DAC audio example start");
ESP_LOGI(TAG, "--------------------------------------"); ESP_LOGI(TAG, "--------------------------------------");
/* Allocate continuous channels */
dac_continuous_handle_t dac_handle; dac_continuous_handle_t dac_handle;
dac_continuous_config_t cont_cfg = { dac_continuous_config_t cont_cfg = {
.chan_mask = DAC_CHANNEL_MASK_ALL, .chan_mask = DAC_CHANNEL_MASK_ALL,
.desc_num = 4, .desc_num = 4,
.buf_size = 2048, .buf_size = 2048,
.freq_hz = CONFIG_EXAMPLE_AUDIO_SAMPLE_RATE, .freq_hz = AUDIO_SAMPLE_RATE_HZ,
.clk_src = DAC_DIGI_CLK_SRC_APLL, // Using APLL as clock source to get a wider frequency range
/* Assume the data in buffer is 'A B C D E F' /* Assume the data in buffer is 'A B C D E F'
* DAC_CHANNEL_MODE_SIMUL: * DAC_CHANNEL_MODE_SIMUL:
* - channel 0: A B C D E F * - channel 0: A B C D E F
@@ -94,28 +96,31 @@ void app_main(void)
*/ */
.chan_mode = DAC_CHANNEL_MODE_SIMUL, .chan_mode = DAC_CHANNEL_MODE_SIMUL,
}; };
/* Allocate continuous channels */
ESP_ERROR_CHECK(dac_continuous_new_channels(&cont_cfg, &dac_handle)); ESP_ERROR_CHECK(dac_continuous_new_channels(&cont_cfg, &dac_handle));
/* Ensure that the bit width configured in menuconfig matches that of the DAC channel */
assert(CONFIG_EXAMPLE_DAC_AUDIO_BITWIDTH == dac_continuous_get_bitwidth(dac_handle));
#if CONFIG_EXAMPLE_DAC_WRITE_ASYNC #if CONFIG_EXAMPLE_DAC_WRITE_ASYNC
/* Create a queue to transport the interrupt event data */ /* Create a queue to transport the interrupt event data */
QueueHandle_t que = xQueueCreate(10, sizeof(dac_event_data_t)); QueueHandle_t que = xQueueCreate(10, sizeof(dac_event_data_t));
assert(que); assert(que);
dac_event_callbacks_t cbs = { dac_event_callbacks_t cbs = {
.on_convert_done = dac_on_convert_done_callback, .on_convert_done = example_on_convert_done_callback,
.on_stop = NULL, .on_stop = NULL,
}; };
/* Must register the callback if using asynchronous writing */ /* Must register the callback if using asynchronous writing */
ESP_ERROR_CHECK(dac_continuous_register_event_callback(dac_handle, &cbs, que)); ESP_ERROR_CHECK(dac_continuous_register_event_callback(dac_handle, &cbs, que));
#endif #endif
/* Enable the continuous channels */ /* Enable the continuous channels */
ESP_ERROR_CHECK(dac_continuous_enable(dac_handle)); ESP_ERROR_CHECK(dac_continuous_enable(dac_handle));
ESP_LOGI(TAG, "DAC initialized success, DAC DMA is ready"); ESP_LOGI(TAG, "DAC initialized success, DAC DMA is ready");
size_t audio_size = sizeof(audio_table);
#if CONFIG_EXAMPLE_DAC_WRITE_ASYNC #if CONFIG_EXAMPLE_DAC_WRITE_ASYNC
ESP_ERROR_CHECK(dac_continuous_start_async_writing(dac_handle)); ESP_ERROR_CHECK(dac_continuous_start_async_writing(dac_handle));
dac_write_data_asynchronously(dac_handle, que, (uint8_t *)audio_table, audio_size); example_write_data_asynchronously(dac_handle, que);
#else #else
dac_write_data_synchronously(dac_handle, (uint8_t *)audio_table, audio_size); example_write_data_synchronously(dac_handle);
#endif #endif
} }

View File

@@ -1,48 +1,144 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0 # SPDX-License-Identifier: Unlicense OR CC0-1.0
import os """Convert one WAV file to a C array of unsigned DAC codes.
8-bit codes are packed as uint8_t; 10-bit and 12-bit codes are packed as uint16_t.
Examples:
python generate_audio_file.py --bitwidth 12 -o audio_example_file.h hi_idf_audio.wav
"""
import struct import struct
import wave import wave
from collections.abc import Iterable
from collections.abc import Iterator
from itertools import islice
from pathlib import Path
from typing import NamedTuple
from typing import TypeVar
try: import rich_click as click
from typing import List from esp_pylib.logger import log
except ImportError:
pass SUPPORTED_BITWIDTHS = (8, 10, 12)
C_ELEM_TYPES = {
8: 'uint8_t',
10: 'uint16_t',
12: 'uint16_t',
}
T = TypeVar('T')
def get_wave_array_str(filename, target_bits): # type: (str, int) -> str class PcmData(NamedTuple):
wave_read = wave.open(filename, 'r') samples: list[int]
array_str = '' src_bits: int
nchannels, sampwidth, framerate, nframes, comptype, compname = wave_read.getparams() sample_rate: int
sampwidth *= 8
for i in range(wave_read.getnframes()):
val, = struct.unpack('<H', wave_read.readframes(1))
scale_val = (1 << target_bits) - 1
cur_lim = (1 << sampwidth) - 1
# scale current data to 8-bit data
val = val * scale_val / cur_lim
val = int(val + ((scale_val + 1) // 2)) & scale_val
array_str += '0x%x, ' % (val)
if (i + 1) % 16 == 0:
array_str += '\n'
return array_str
def gen_wave_table(wav_file_list, target_file_name, scale_bits=8): # type: (List[str], str, int) -> None def _read_signed_pcm(filename: Path) -> PcmData:
with open(target_file_name, 'w') as audio_table: """Read signed samples and their bit width from a PCM WAV file.
print('#include <stdio.h>', file=audio_table)
print('const unsigned char audio_table[] = {', file=audio_table) Multi-channel files use the first channel only. 8-bit WAV is unsigned and is
for wav in wav_file_list: converted to signed PCM; 16-bit WAV is treated as signed little-endian.
print('processing: {}'.format(wav)) """
print(get_wave_array_str(filename=wav, target_bits=scale_bits), file=audio_table) with wave.open(str(filename), 'r') as wav:
print('};\n', file=audio_table) params = wav.getparams()
print('Done...') raw = wav.readframes(params.nframes)
if params.sampwidth == 1:
src_bits = 8
signed = [sample - 128 for sample in raw]
elif params.sampwidth == 2:
src_bits = 16
signed = list(struct.unpack(f'<{params.nframes * params.nchannels}h', raw))
else:
raise ValueError(
f'{filename}: only 8-bit and 16-bit PCM WAV are supported (got {params.sampwidth}-byte samples)'
)
if params.nchannels > 1:
signed = signed[0 :: params.nchannels]
return PcmData(signed, src_bits, params.framerate)
def pcm_to_dac_code(pcm: int, src_bits: int, target_bits: int) -> int:
"""Map signed PCM to an unsigned DAC code with mid-scale bias."""
max_code = (1 << target_bits) - 1
mid = 1 << (target_bits - 1)
shift = src_bits - target_bits
if shift >= 0:
code = (pcm >> shift) + mid
else:
code = (pcm << -shift) + mid
return max(0, min(code, max_code))
def _chunked(iterable: Iterable[T], size: int) -> Iterator[list[T]]:
if size <= 0:
raise ValueError('chunk size must be positive')
iterator = iter(iterable)
while chunk := list(islice(iterator, size)):
yield chunk
def get_wave_array_str(pcm_data: PcmData, target_bits: int) -> str:
hex_width = (target_bits + 3) // 4
codes = (f'0x{pcm_to_dac_code(pcm, pcm_data.src_bits, target_bits):0{hex_width}x},' for pcm in pcm_data.samples)
return '\n'.join(' '.join(chunk) for chunk in _chunked(codes, 16))
def c_elem_type(bitwidth: int) -> str:
return C_ELEM_TYPES[bitwidth]
def gen_wave_table(wav_file: Path, target_file_name: Path, scale_bits: int = 8) -> None:
pcm_data = _read_signed_pcm(wav_file)
elem_type = c_elem_type(scale_bits)
with target_file_name.open('w', encoding='utf-8') as audio_table:
audio_table.write('#include <stdint.h>\n')
audio_table.write(f'#define AUDIO_SAMPLE_RATE_HZ {pcm_data.sample_rate}\n\n')
audio_table.write(f'const {elem_type} audio_table[] = {{\n')
audio_table.write(get_wave_array_str(pcm_data=pcm_data, target_bits=scale_bits))
audio_table.write('\n};\n')
@click.command(
context_settings={'help_option_names': ['-h', '--help']},
help='Generate a C array of DAC codes from one WAV file.',
)
@click.option(
'-b',
'--bitwidth',
type=click.Choice(tuple(str(bitwidth) for bitwidth in SUPPORTED_BITWIDTHS)),
default='8',
show_default=True,
help='DAC code bit width.',
)
@click.option(
'-o',
'--output',
type=click.Path(dir_okay=False, path_type=Path),
default='audio_example_file.h',
show_default=True,
help='Output header path.',
)
@click.argument(
'wav_file',
type=click.Path(exists=True, dir_okay=False, readable=True, path_type=Path),
)
def cli(bitwidth: str, output: Path, wav_file: Path) -> None:
scale_bits = int(bitwidth)
log.print(f'Generating audio array from {wav_file} (bitwidth={scale_bits})...', markup=False, soft_wrap=True)
gen_wave_table(wav_file=wav_file, target_file_name=output, scale_bits=scale_bits)
log.print(f'Wrote {output}', markup=False, soft_wrap=True)
def main() -> None:
cli()
if __name__ == '__main__': if __name__ == '__main__':
print('Generating audio array...') from esp_pylib.excepthook import install_exception_reporting
wav_list = []
for wavefile in os.listdir('./'): install_exception_reporting()
if wavefile.endswith('.wav'): main()
wav_list.append(wavefile)
gen_wave_table(wav_file_list=wav_list, target_file_name='audio_example_file.h')

View File

@@ -1,40 +1,43 @@
| Supported Targets | ESP32 | ESP32-S2 | | Supported Targets | ESP32 | ESP32-S2 |
| ----------------- | ----- | -------- | | ----------------- | ----- | -------- |
# DAC Constant Example # DAC Continuous Signal Generator Example
(See the README.md file in the upper level 'examples' directory for more information about examples.) (See the README.md file in the upper level 'examples' directory for more information about examples.)
## Overview ## Overview
This example shows the basic usage of outputting continuous voltage by the DAC driver. There are two ways to realize continuous output, one is outputting by DMA transmission and another is by timer interrupt. This example demonstrates how to continuously output waveforms using the DAC. There are two general approaches: DMA transfer and timer interrupts.
### Timer Interrupt ### DMA Transfer
While using timer interrupt to output the waves, it actually sets the voltage by `oneshot` API in every timer interrupt callback. Which means the conversion frequency is equal to the timer interrupt frequency. Obviously, the conversion frequency is limited by the interrupt, which relies on the CPU scheduling, thus it can't reach a high frequency in this mode. But it can be used as a supplementary way while the conversion frequency is too low to use DMA mode. Waveform samples are filled into a DMA buffer and continuously transferred to the DAC by DMA. Therefore, the sample (or DAC code) update rate is equal to the DMA transfer rate. This approach reduces CPU load and supports relatively high update rates. However, due to DMA clock limitations, very low update rates may not be achievable. In addition, due to the analog performance limitations of the DAC, excessively high frequencies may result in waveform distortion.
### DMA transmission ### Timer Interrupts
While using DMA to transmit the wave buffers, the digital values are put into a DMA buffer waiting for transmission and conversion, that means the conversion frequency is equal to the frequency that DMA transmitting the data. We can set the DMA frequency directly, and the digital data in the buffer will be sent automatically when the buffer has been loaded into the DMA. So the conversion frequency can reach even several MHz while using DMA mode. But the wave can be distorted if the frequency is too high. This approach essentially calls the DAC `oneshot` API from a timer interrupt callback. Therefore, the sample (or DAC code) update rate is determined by the timer interrupt frequency. Note that frequent interrupts can impose significant CPU overhead, so this approach is only suitable for relatively low update rates.
In general, the DMA-based approach is recommended for most applications, unless the DMA peripheral is already occupied or the required sample update rate is too low.
## How to use the Example ## How to use the Example
### Hardware Required ### Hardware Required
* A development board with ESP32 or ESP32-S2 SoC * A development board with ESP32 or ESP32-S2 SoC
- Note that some ESP32-S2 DevKits have LED on it which is connected to GPIO18 (same pin as DAC channel1), so the output voltage of DAC channel 1 can't go down due the this LED. * DAC channel to GPIO mapping: see [GPIO Summary](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/gpio.html#gpio-summary) (switch the chip target on the documentation page if needed)
* Note that some ESP32-S2 DevKits have an LED on the DAC1 pin, so that channel's output may not go fully low
* (Optional) An oscilloscope to monitor the output wave * (Optional) An oscilloscope to monitor the output wave
### Configure the Project ### Configure the Project
You can switch the output method by setting the macro `EXAMPLE_DAC_CONTINUOUS_MODE` to `EXAMPLE_DAC_CONTINUOUS_BY_TIMER` or `EXAMPLE_DAC_CONTINUOUS_BY_DMA`. You can switch the output method in menuconfig under ``Example Configuration`` → ``Select DAC continuous example mode`` (DMA or Timer).
There are four waves: sine, triangle, saw tooth and square. These waves are stored in corresponding buffers, and each wave has 400 points as default, which can be modified by `EXAMPLE_ARRAY_LEN`, reduce the point number can increase the wave frequency. Four waveform types are supported: sine, triangle, sawtooth, and square waves. The waveform data is generated at runtime and stored in the corresponding buffers. Each waveform buffer contains one complete period with 400 samples by default, which can be changed via `EXAMPLE_ARRAY_LEN`. The following relationship applies:
Output waveform frequency = DAC sample update rate / Number of samples
### Build and Flash ### Build and Flash
Note that as we use the ADC to monitor the output data, we need to set false to `CONFIG_ADC_DISABLE_DAC_OUTPUT` in the menuconfig, otherwise the ADC will shutdown the DAC power to guarantee it won't be affect by DAC.
Build the project and flash it to the board, then run monitor tool to view serial output: Build the project and flash it to the board, then run monitor tool to view serial output:
``` ```
@@ -49,107 +52,41 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui
## Example Output ## Example Output
This example can output sine wave, triangle wave, saw tooth wave and square wave periodically, each wave will last for 3 seconds. This example outputs sine, triangle, sawtooth and square waves on DAC channel 0, switching every 2 seconds. Connect an oscilloscope to the DAC channel 0 GPIO to observe the waveforms.
The DAC channels can be read by ADC channels internally. The ADC read period is 500 ms, the following log is the raw ADC value read from the DAC channels. But since the ADC sample-rate is lower than the DAC output-rate, the sampling value can only indicate that the voltage is changing.
### Timer Triggered Output
You can see sine wave, triangle wave, saw tooth wave and square wave at 50 Hz on the oscilloscope.
```
I (333) dac continuous: --------------------------------------------------
I (343) dac continuous: DAC continuous output by Timer
I (343) dac continuous: DAC channel 0 io: GPIO_NUM_25
I (353) dac continuous: DAC channel 1 io: GPIO_NUM_26
I (353) dac continuous: Waveform: SINE -> TRIANGLE -> SAWTOOTH -> SQUARE
I (363) dac continuous: DAC conversion frequency (Hz): 20000
I (373) dac continuous: DAC wave frequency (Hz): 50
I (373) dac continuous: --------------------------------------------------
DAC channel 0 value: 2291 DAC channel 1 value: 2331
DAC channel 0 value: 43 DAC channel 1 value: 3
DAC channel 0 value: 55 DAC channel 1 value: 32
DAC channel 0 value: 57 DAC channel 1 value: 33
DAC channel 0 value: 56 DAC channel 1 value: 34
DAC channel 0 value: 59 DAC channel 1 value: 34
DAC channel 0 value: 56 DAC channel 1 value: 33
I (3393) dac continuous(timer): triangle wave start
DAC channel 0 value: 2258 DAC channel 1 value: 2243
DAC channel 0 value: 2257 DAC channel 1 value: 2242
DAC channel 0 value: 2259 DAC channel 1 value: 2242
DAC channel 0 value: 2257 DAC channel 1 value: 2245
DAC channel 0 value: 2257 DAC channel 1 value: 2243
DAC channel 0 value: 2258 DAC channel 1 value: 2240
I (6393) dac continuous(timer): sawtooth wave start
DAC channel 0 value: 2704 DAC channel 1 value: 2735
DAC channel 0 value: 2704 DAC channel 1 value: 2735
DAC channel 0 value: 2704 DAC channel 1 value: 2736
DAC channel 0 value: 2704 DAC channel 1 value: 2717
DAC channel 0 value: 2704 DAC channel 1 value: 2734
DAC channel 0 value: 2704 DAC channel 1 value: 2736
I (9393) dac continuous(timer): square wave start
DAC channel 0 value: 0 DAC channel 1 value: 0
DAC channel 0 value: 0 DAC channel 1 value: 0
DAC channel 0 value: 0 DAC channel 1 value: 0
DAC channel 0 value: 0 DAC channel 1 value: 0
DAC channel 0 value: 0 DAC channel 1 value: 0
DAC channel 0 value: 0 DAC channel 1 value: 0
I (12393) dac continuous(timer): sine wave start
DAC channel 0 value: 82 DAC channel 1 value: 62
DAC channel 0 value: 83 DAC channel 1 value: 62
DAC channel 0 value: 82 DAC channel 1 value: 62
DAC channel 0 value: 87 DAC channel 1 value: 62
DAC channel 0 value: 84 DAC channel 1 value: 63
DAC channel 0 value: 83 DAC channel 1 value: 64
...
```
### DMA Output ### DMA Output
You can see sine wave, triangle wave, saw tooth wave and square wave at 2 KHz on the oscilloscope. You can see sine wave, triangle wave, saw tooth wave and square wave at 2 KHz on the oscilloscope.
``` ```
I (335) dac continuous: -------------------------------------------------- I (266) signal_generator: --------------------------------------------------
I (345) dac continuous: DAC continuous output by DMA I (266) signal_generator: DAC continuous output by DMA
I (345) dac continuous: DAC channel 0 io: GPIO_NUM_25 I (266) signal_generator: DAC channel 0 io: GPIO_NUM_25
I (355) dac continuous: DAC channel 1 io: GPIO_NUM_26 I (276) signal_generator: Waveform: SINE -> TRIANGLE -> SAWTOOTH -> SQUARE
I (355) dac continuous: Waveform: SINE -> TRIANGLE -> SAWTOOTH -> SQUARE I (276) signal_generator: DAC sample update rate (Hz): 800000
I (365) dac continuous: DAC conversion frequency (Hz): 800000 I (286) signal_generator: Waveform frequency (Hz): 2000
I (375) dac continuous: DAC wave frequency (Hz): 2000 I (286) signal_generator: --------------------------------------------------
I (375) dac continuous: -------------------------------------------------- I (296) signal_generator: sine wave start
DAC channel 0 value: 3131 DAC channel 1 value: 1634 I (2296) signal_generator: triangle wave start
DAC channel 0 value: 1712 DAC channel 1 value: 2531 I (4296) signal_generator: sawtooth wave start
DAC channel 0 value: 1716 DAC channel 1 value: 2535 I (6296) signal_generator: square wave start
DAC channel 0 value: 1715 DAC channel 1 value: 2544 ...
DAC channel 0 value: 1715 DAC channel 1 value: 2533 ```
DAC channel 0 value: 1712 DAC channel 1 value: 2539 ### Timer Triggered Output
I (3395) dac continuous(DMA): triangle wave start
DAC channel 0 value: 592 DAC channel 1 value: 1190 You can see sine wave, triangle wave, saw tooth wave and square wave at 50 Hz on the oscilloscope.
DAC channel 0 value: 4095 DAC channel 1 value: 3518
DAC channel 0 value: 4095 DAC channel 1 value: 3515 ```
DAC channel 0 value: 4095 DAC channel 1 value: 3516 I (265) signal_generator: --------------------------------------------------
DAC channel 0 value: 4095 DAC channel 1 value: 3514 I (265) signal_generator: DAC continuous output by timer
DAC channel 0 value: 4095 DAC channel 1 value: 3515 I (265) signal_generator: DAC channel 0 io: GPIO_NUM_25
I (6395) dac continuous(DMA): sawtooth wave start I (275) signal_generator: Waveform: SINE -> TRIANGLE -> SAWTOOTH -> SQUARE
DAC channel 0 value: 294 DAC channel 1 value: 560 I (275) signal_generator: DAC sample update rate (Hz): 20000
DAC channel 0 value: 2861 DAC channel 1 value: 3227 I (285) signal_generator: Waveform frequency (Hz): 50
DAC channel 0 value: 2860 DAC channel 1 value: 3216 I (285) signal_generator: --------------------------------------------------
DAC channel 0 value: 2861 DAC channel 1 value: 3227 I (295) signal_generator: sine wave start
DAC channel 0 value: 2861 DAC channel 1 value: 3216 I (2295) signal_generator: triangle wave start
DAC channel 0 value: 2859 DAC channel 1 value: 3183 I (4295) signal_generator: sawtooth wave start
I (9395) dac continuous(DMA): square wave start I (6295) signal_generator: square wave start
DAC channel 0 value: 4095 DAC channel 1 value: 4095
DAC channel 0 value: 0 DAC channel 1 value: 0
DAC channel 0 value: 0 DAC channel 1 value: 0
DAC channel 0 value: 0 DAC channel 1 value: 0
DAC channel 0 value: 0 DAC channel 1 value: 0
DAC channel 0 value: 0 DAC channel 1 value: 0
I (12395) dac continuous(DMA): sine wave start
DAC channel 0 value: 2864 DAC channel 1 value: 3691
DAC channel 0 value: 0 DAC channel 1 value: 204
DAC channel 0 value: 0 DAC channel 1 value: 202
DAC channel 0 value: 0 DAC channel 1 value: 193
DAC channel 0 value: 0 DAC channel 1 value: 181
DAC channel 0 value: 0 DAC channel 1 value: 194
... ...
``` ```

View File

@@ -1,7 +1,5 @@
set(srcs "dac_continuous_example_main.c" idf_component_register(SRCS "dac_continuous_example_main.c"
"dac_continuous_example_dma.c" "dac_continuous_example_dma.c"
"dac_continuous_example_timer.c") "dac_continuous_example_timer.c"
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "." INCLUDE_DIRS "."
PRIV_REQUIRES esp_driver_dac esp_driver_gpio esp_driver_gptimer esp_adc) PRIV_REQUIRES esp_driver_dac esp_driver_gptimer)

View File

@@ -1,12 +1,24 @@
/* /*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: CC0-1.0 * SPDX-License-Identifier: CC0-1.0
*/ */
#define CONST_PERIOD_2_PI 6.2832 // 2 * PI #include <stdint.h>
#include "soc/soc_caps.h"
#define EXAMPLE_ARRAY_LEN 400 // Length of wave array #define TAG "signal_generator"
#define EXAMPLE_DAC_AMPLITUDE 255 // Amplitude of DAC voltage. If it's more than 256 will causes dac_output_voltage() output 0.
#if SOC_IS(ESP32) || SOC_IS(ESP32S2)
/* only support 8-bit code, packed into uint8_t */
typedef uint8_t dac_example_sample_t;
#elif SOC_IS(ESP32S31)
/* support 10-bit and 12-bit codes, both packed into uint16_t */
typedef uint16_t dac_example_sample_t;
#else
#error "Define packed sample type to match selected SOC and DAC channel config"
#endif
#define EXAMPLE_ARRAY_LEN 400 // Length of wave array
typedef enum { typedef enum {
DAC_SINE_WAVE, DAC_SINE_WAVE,
@@ -16,6 +28,16 @@ typedef enum {
DAC_WAVE_MAX, DAC_WAVE_MAX,
} dac_example_wave_type_t; } dac_example_wave_type_t;
extern const char* wave_name[DAC_WAVE_MAX];
extern dac_example_sample_t wave_data[DAC_WAVE_MAX][EXAMPLE_ARRAY_LEN];
/**
* @brief Generate wave tables for the given bit width of the configured DAC channel
*
* @param resolution_bits The resolution bits width of the DAC channel
*/
void example_generate_wave(uint8_t resolution_bits);
/** /**
* @brief Use DMA to convert continuously * @brief Use DMA to convert continuously
* *
@@ -31,7 +53,7 @@ void example_dac_continuous_by_timer(void);
/** /**
* @brief Print the example log information * @brief Print the example log information
* *
* @param conv_freq DAC conversion frequency * @param update_rate DAC sample update rate (Hz)
* @param wave_freq The frequency of the wave * @param wave_freq The frequency of the wave
*/ */
void example_log_info(uint32_t conv_freq, uint32_t wave_freq); void example_log_info(uint32_t update_rate, uint32_t wave_freq);

View File

@@ -1,58 +1,31 @@
/* /*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: CC0-1.0 * SPDX-License-Identifier: CC0-1.0
*/ */
#include <math.h>
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "driver/dac_continuous.h" #include "driver/dac_continuous.h"
#include "esp_check.h" #include "esp_check.h"
#include "dac_continuous_example.h" #include "dac_continuous_example.h"
#define EXAMPLE_WAVE_FREQ_HZ 2000 // Default wave frequency 2000 Hz, it can't be too low #define EXAMPLE_WAVE_FREQ_HZ 2000 // Default wave frequency 2000 Hz, it can't be too low
#define EXAMPLE_CONVERT_FREQ_HZ (EXAMPLE_ARRAY_LEN * EXAMPLE_WAVE_FREQ_HZ) // The frequency that DAC convert every data in the wave array #define EXAMPLE_UPDATE_RATE_HZ (EXAMPLE_ARRAY_LEN * EXAMPLE_WAVE_FREQ_HZ) // The frequency at which the DAC samples (codes) are updated
extern uint8_t sin_wav[EXAMPLE_ARRAY_LEN]; // Used to store sine wave values
extern uint8_t tri_wav[EXAMPLE_ARRAY_LEN]; // Used to store triangle wave values
extern uint8_t saw_wav[EXAMPLE_ARRAY_LEN]; // Used to store sawtooth wave values
extern uint8_t squ_wav[EXAMPLE_ARRAY_LEN]; // Used to store square wave values
static const char *TAG = "dac continuous(DMA)";
static const char wav_name[DAC_WAVE_MAX][15] = {"sine", "triangle", "sawtooth", "square"};
static void dac_dma_write_task(void *args) static void dac_dma_write_task(void *args)
{ {
dac_continuous_handle_t handle = (dac_continuous_handle_t)args; dac_continuous_handle_t handle = (dac_continuous_handle_t)args;
dac_example_wave_type_t wav_sel = DAC_SINE_WAVE; // Start from sine wave dac_example_wave_type_t wave_sel = DAC_SINE_WAVE; // Start from sine wave
size_t buf_len = EXAMPLE_ARRAY_LEN;
while (1) { while (1) {
ESP_LOGI(TAG, "%s wave start", wav_name[wav_sel]); ESP_LOGI(TAG, "%s wave start", wave_name[wave_sel]);
/* The wave in the buffer will be converted cyclically */ /* The wave in the buffer will be converted cyclically */
switch (wav_sel) { ESP_ERROR_CHECK(dac_continuous_write_cyclically(handle, wave_data[wave_sel], EXAMPLE_ARRAY_LEN, NULL));
case DAC_SINE_WAVE:
ESP_ERROR_CHECK(dac_continuous_write_cyclically(handle, (uint8_t *)sin_wav, buf_len, NULL));
break;
case DAC_TRIANGLE_WAVE:
ESP_ERROR_CHECK(dac_continuous_write_cyclically(handle, (uint8_t *)tri_wav, buf_len, NULL));
break;
case DAC_SAWTOOTH_WAVE:
ESP_ERROR_CHECK(dac_continuous_write_cyclically(handle, (uint8_t *)saw_wav, buf_len, NULL));
break;
case DAC_SQUARE_WAVE:
ESP_ERROR_CHECK(dac_continuous_write_cyclically(handle, (uint8_t *)squ_wav, buf_len, NULL));
break;
default:
break;
}
/* Switch wave every CONFIG_EXAMPLE_WAVE_PERIOD_SEC seconds */ /* Switch wave every CONFIG_EXAMPLE_WAVE_PERIOD_SEC seconds */
vTaskDelay(pdMS_TO_TICKS(CONFIG_EXAMPLE_WAVE_PERIOD_SEC * 1000)); vTaskDelay(pdMS_TO_TICKS(CONFIG_EXAMPLE_WAVE_PERIOD_SEC * 1000));
ESP_ERROR_CHECK(dac_continuous_stop_cyclically(handle)); ESP_ERROR_CHECK(dac_continuous_stop_cyclically(handle));
wav_sel++; wave_sel = (wave_sel + 1) % DAC_WAVE_MAX;
wav_sel %= DAC_WAVE_MAX;
} }
} }
@@ -60,26 +33,19 @@ void example_dac_continuous_by_dma(void)
{ {
dac_continuous_handle_t cont_handle; dac_continuous_handle_t cont_handle;
dac_continuous_config_t cont_cfg = { dac_continuous_config_t cont_cfg = {
.chan_mask = DAC_CHANNEL_MASK_ALL, .chan_mask = DAC_CHANNEL_MASK_CH0, // This example outputs on one channel only
.desc_num = 8, .desc_num = 8,
.buf_size = 2048, .buf_size = 2048,
.freq_hz = EXAMPLE_CONVERT_FREQ_HZ, .freq_hz = EXAMPLE_UPDATE_RATE_HZ,
/* Assume the data in buffer is 'A B C D E F'
* DAC_CHANNEL_MODE_SIMUL:
* - channel 0: A B C D E F
* - channel 1: A B C D E F
* DAC_CHANNEL_MODE_ALTER:
* - channel 0: A C E
* - channel 1: B D F
*/
.chan_mode = DAC_CHANNEL_MODE_SIMUL,
}; };
/* Allocate continuous channel */ /* Allocate continuous channel */
ESP_ERROR_CHECK(dac_continuous_new_channels(&cont_cfg, &cont_handle)); ESP_ERROR_CHECK(dac_continuous_new_channels(&cont_cfg, &cont_handle));
/* Generate wave data */
example_generate_wave(dac_continuous_get_bitwidth(cont_handle));
/* Enable the channels in the group */ /* Enable the channels in the group */
ESP_ERROR_CHECK(dac_continuous_enable(cont_handle)); ESP_ERROR_CHECK(dac_continuous_enable(cont_handle));
example_log_info(EXAMPLE_CONVERT_FREQ_HZ, EXAMPLE_WAVE_FREQ_HZ); example_log_info(EXAMPLE_UPDATE_RATE_HZ, EXAMPLE_WAVE_FREQ_HZ);
/* Start to convert wave */ /* Start to convert wave */
xTaskCreate(dac_dma_write_task, "dac_dma_write_task", 4096, cont_handle, 5, NULL); xTaskCreate(dac_dma_write_task, "dac_dma_write_task", 4096, cont_handle, 5, NULL);

View File

@@ -1,67 +1,36 @@
/* /*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: CC0-1.0 * SPDX-License-Identifier: CC0-1.0
*/ */
#include <stdint.h>
#include <inttypes.h> #include <inttypes.h>
#include <math.h> #include <math.h>
#include "freertos/FreeRTOS.h" #include "esp_log.h"
#include "freertos/task.h" #include "hal/dac_types.h"
#include "soc/dac_channel.h" #include "hal/dac_periph.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_check.h"
#include "dac_continuous_example.h" #include "dac_continuous_example.h"
/** #define EXAMPLE_DAC_CHAN_IO dac_periph_signal.dac_channel_io_num[DAC_CHAN_0]
* There are two ways to convert digital data to analog signal continuously:
* - Using a timer: setting DAC voltage periodically in the timer interrupt
* in this way, DAC can achieve a relatively low conversion frequency
* but it is not a efficient way comparing to using the DMA
* - Using DMA: tansmitting the data buffer via DMA,
* the conversion frequency is controlled by how fast it is transmitted by DMA
* in this way, the conversion frequency can reach several MHz,
* but it can't achieve a very low conversion frequency because it is limited by the DMA clock source
* Generally, recommand to use DMA, if the DMA peripheral is occupied or the required conversion frequency is very low,
* then use timer instead
*/
/* ADC configuration */ const char* wave_name[DAC_WAVE_MAX] = {"sine", "triangle", "sawtooth", "square"};
#if CONFIG_IDF_TARGET_ESP32 dac_example_sample_t wave_data[DAC_WAVE_MAX][EXAMPLE_ARRAY_LEN];
#define EXAMPLE_DAC_CHAN0_ADC_CHAN ADC_CHANNEL_8 // GPIO25, same as DAC channel 0
#define EXAMPLE_DAC_CHAN1_ADC_CHAN ADC_CHANNEL_9 // GPIO26, same as DAC channel 1
#define EXAMPLE_ADC_WIDTH ADC_WIDTH_BIT_12
#elif CONFIG_IDF_TARGET_ESP32S2
#define EXAMPLE_DAC_CHAN0_ADC_CHAN ADC_CHANNEL_6 // GPIO17, same as DAC channel 0
#define EXAMPLE_DAC_CHAN1_ADC_CHAN ADC_CHANNEL_7 // GPIO18, same as DAC channel 1
#define EXAMPLE_ADC_WIDTH ADC_WIDTH_BIT_13
#endif
#define EXAMPLE_DAC_CHAN0_IO DAC_CHAN0_GPIO_NUM // DAC channel 0 io number
#define EXAMPLE_DAC_CHAN1_IO DAC_CHAN1_GPIO_NUM // DAC channel 1 io number
#define EXAMPLE_ADC_ATTEN ADC_ATTEN_DB_12
_Static_assert(EXAMPLE_DAC_AMPLITUDE < 256, "The DAC accuracy is 8 bit-width, doesn't support the amplitude beyond 255"); void example_generate_wave(uint8_t resolution_bits)
static const char *TAG = "dac continuous";
uint8_t sin_wav[EXAMPLE_ARRAY_LEN]; // Used to store sine wave values
uint8_t tri_wav[EXAMPLE_ARRAY_LEN]; // Used to store triangle wave values
uint8_t saw_wav[EXAMPLE_ARRAY_LEN]; // Used to store sawtooth wave values
uint8_t squ_wav[EXAMPLE_ARRAY_LEN]; // Used to store square wave values
static void example_generate_wave(void)
{ {
uint32_t pnt_num = EXAMPLE_ARRAY_LEN; const size_t N = EXAMPLE_ARRAY_LEN;
const uint16_t max_code = (1U << resolution_bits) - 1;
for (int i = 0; i < pnt_num; i ++) { for (size_t i = 0; i < N; i++) {
sin_wav[i] = (uint8_t)((sin(i * CONST_PERIOD_2_PI / pnt_num) + 1) * (double)(EXAMPLE_DAC_AMPLITUDE) / 2 + 0.5); wave_data[DAC_SINE_WAVE][i] = (sin(2 * M_PI * i / N) + 1) / 2 * max_code + 0.5;
tri_wav[i] = (i > (pnt_num / 2)) ? (2 * EXAMPLE_DAC_AMPLITUDE * (pnt_num - i) / pnt_num) : (2 * EXAMPLE_DAC_AMPLITUDE * i / pnt_num); wave_data[DAC_TRIANGLE_WAVE][i] = (i < N / 2) ? (2 * max_code * i / N) : (2 * max_code * (N - i) / N);
saw_wav[i] = (i == pnt_num) ? 0 : (i * EXAMPLE_DAC_AMPLITUDE / pnt_num); wave_data[DAC_SAWTOOTH_WAVE][i] = max_code * i / N;
squ_wav[i] = (i < (pnt_num / 2)) ? EXAMPLE_DAC_AMPLITUDE : 0; wave_data[DAC_SQUARE_WAVE][i] = (i < N / 2) ? max_code : 0;
} }
} }
void example_log_info(uint32_t conv_freq, uint32_t wave_freq) void example_log_info(uint32_t update_rate, uint32_t wave_freq)
{ {
ESP_LOGI(TAG, "--------------------------------------------------"); ESP_LOGI(TAG, "--------------------------------------------------");
#if CONFIG_EXAMPLE_DAC_CONTINUOUS_BY_DMA #if CONFIG_EXAMPLE_DAC_CONTINUOUS_BY_DMA
@@ -69,32 +38,15 @@ void example_log_info(uint32_t conv_freq, uint32_t wave_freq)
#else #else
ESP_LOGI(TAG, "DAC continuous output by timer"); ESP_LOGI(TAG, "DAC continuous output by timer");
#endif #endif
ESP_LOGI(TAG, "DAC channel 0 io: GPIO_NUM_%d", EXAMPLE_DAC_CHAN0_IO); ESP_LOGI(TAG, "DAC channel 0 io: GPIO_NUM_%d", EXAMPLE_DAC_CHAN_IO);
ESP_LOGI(TAG, "DAC channel 1 io: GPIO_NUM_%d", EXAMPLE_DAC_CHAN1_IO);
ESP_LOGI(TAG, "Waveform: SINE -> TRIANGLE -> SAWTOOTH -> SQUARE"); ESP_LOGI(TAG, "Waveform: SINE -> TRIANGLE -> SAWTOOTH -> SQUARE");
ESP_LOGI(TAG, "DAC conversion frequency (Hz): %"PRIu32, conv_freq); ESP_LOGI(TAG, "DAC sample update rate (Hz): %"PRIu32, update_rate);
ESP_LOGI(TAG, "DAC wave frequency (Hz): %"PRIu32, wave_freq); ESP_LOGI(TAG, "Waveform frequency (Hz): %"PRIu32, wave_freq);
ESP_LOGI(TAG, "--------------------------------------------------"); ESP_LOGI(TAG, "--------------------------------------------------");
} }
static void adc_monitor_task(void *args)
{
adc_oneshot_unit_handle_t adc2_handle = (adc_oneshot_unit_handle_t)args;
int chan0_val = 0;
int chan1_val = 0;
while (1) {
/* Read the DAC output voltage */
ESP_ERROR_CHECK(adc_oneshot_read(adc2_handle, EXAMPLE_DAC_CHAN0_ADC_CHAN, &chan0_val));
ESP_ERROR_CHECK(adc_oneshot_read(adc2_handle, EXAMPLE_DAC_CHAN1_ADC_CHAN, &chan1_val));
printf("DAC channel 0 value: %4d\tDAC channel 1 value: %4d\n", chan0_val, chan1_val);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void app_main(void) void app_main(void)
{ {
example_generate_wave();
#if CONFIG_EXAMPLE_DAC_CONTINUOUS_BY_DMA #if CONFIG_EXAMPLE_DAC_CONTINUOUS_BY_DMA
/* Output 2 kHz waves using DMA */ /* Output 2 kHz waves using DMA */
example_dac_continuous_by_dma(); example_dac_continuous_by_dma();
@@ -102,20 +54,4 @@ void app_main(void)
/* Output 50 Hz waves using timer interrupt */ /* Output 50 Hz waves using timer interrupt */
example_dac_continuous_by_timer(); example_dac_continuous_by_timer();
#endif #endif
/* Set the ADC2 channels, these channels are connected to the DAC channels internally */
adc_oneshot_unit_handle_t adc2_handle;
adc_oneshot_unit_init_cfg_t adc_cfg = {
.unit_id = ADC_UNIT_2,
.ulp_mode = false,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&adc_cfg, &adc2_handle));
adc_oneshot_chan_cfg_t chan_cfg = {
.atten = EXAMPLE_ADC_ATTEN,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc2_handle, EXAMPLE_DAC_CHAN0_ADC_CHAN, &chan_cfg));
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc2_handle, EXAMPLE_DAC_CHAN1_ADC_CHAN, &chan_cfg));
/* Create ADC monitor task to detect the voltage on DAC pin */
xTaskCreate(adc_monitor_task, "adc_monitor_task", 4096, adc2_handle, 5, NULL);
} }

View File

@@ -1,17 +1,9 @@
/* /*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: CC0-1.0 * SPDX-License-Identifier: CC0-1.0
*/ */
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "driver/gptimer.h" #include "driver/gptimer.h"
#include "driver/dac_oneshot.h" #include "driver/dac_oneshot.h"
#include "esp_log.h" #include "esp_log.h"
@@ -19,57 +11,32 @@
#define EXAMPLE_TIMER_RESOLUTION 1000000 // 1MHz, 1 tick = 1us #define EXAMPLE_TIMER_RESOLUTION 1000000 // 1MHz, 1 tick = 1us
#define EXAMPLE_WAVE_FREQ_HZ 50 // Default wave frequency 50 Hz, it can't be too high #define EXAMPLE_WAVE_FREQ_HZ 50 // Default wave frequency 50 Hz, it can't be too high
#define EXAMPLE_CONVERT_FREQ_HZ (EXAMPLE_ARRAY_LEN * EXAMPLE_WAVE_FREQ_HZ) // The frequency that DAC convert every data in the wave array #define EXAMPLE_UPDATE_RATE_HZ (EXAMPLE_ARRAY_LEN * EXAMPLE_WAVE_FREQ_HZ) // The frequency at which the DAC samples (codes) are updated
#define EXAMPLE_TIMER_ALARM_COUNT (EXAMPLE_TIMER_RESOLUTION / EXAMPLE_CONVERT_FREQ_HZ) // The count value that trigger the timer alarm callback #define EXAMPLE_TIMER_ALARM_COUNT (EXAMPLE_TIMER_RESOLUTION / EXAMPLE_UPDATE_RATE_HZ) // The count value that trigger the timer alarm callback
static const char *TAG = "dac continuous(timer)";
static const char wav_name[DAC_WAVE_MAX][15] = {"sine", "triangle", "sawtooth", "square"};
static dac_oneshot_handle_t chan0_handle;
static dac_oneshot_handle_t chan1_handle;
extern uint8_t sin_wav[EXAMPLE_ARRAY_LEN]; // Used to store sine wave values
extern uint8_t tri_wav[EXAMPLE_ARRAY_LEN]; // Used to store triangle wave values
extern uint8_t saw_wav[EXAMPLE_ARRAY_LEN]; // Used to store sawtooth wave values
extern uint8_t squ_wav[EXAMPLE_ARRAY_LEN]; // Used to store square wave values
/* Timer interrupt service routine */ /* Timer interrupt service routine */
static bool IRAM_ATTR on_timer_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data) static bool IRAM_ATTR on_timer_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
{ {
static uint32_t point_cnt = 0; // For counting the output points of one wave static uint32_t point_cnt = 0; // For counting the output points of one wave
static uint32_t index = 0; // The current index of the wave buffer static uint32_t index = 0; // The current index of the wave buffer
static dac_example_wave_type_t wav_sel = DAC_SINE_WAVE; // Start from sine wave static dac_example_wave_type_t wave_sel = DAC_SINE_WAVE; // Start from sine wave
// Switch wave every CONFIG_EXAMPLE_WAVE_PERIOD_SEC second dac_oneshot_handle_t dac_handle = user_data;
if (point_cnt < EXAMPLE_CONVERT_FREQ_HZ * CONFIG_EXAMPLE_WAVE_PERIOD_SEC) {
switch (wav_sel) { if (point_cnt == 0) {
case DAC_SINE_WAVE: ESP_EARLY_LOGI(TAG, "%s wave start", wave_name[wave_sel]);
ESP_ERROR_CHECK(dac_oneshot_output_voltage(chan0_handle, sin_wav[index])); }
ESP_ERROR_CHECK(dac_oneshot_output_voltage(chan1_handle, sin_wav[index]));
break; dac_example_sample_t code = wave_data[wave_sel][index];
case DAC_TRIANGLE_WAVE: dac_oneshot_output_voltage(dac_handle, code);
ESP_ERROR_CHECK(dac_oneshot_output_voltage(chan0_handle, tri_wav[index]));
ESP_ERROR_CHECK(dac_oneshot_output_voltage(chan1_handle, tri_wav[index])); point_cnt++;
break; if (point_cnt < EXAMPLE_UPDATE_RATE_HZ * CONFIG_EXAMPLE_WAVE_PERIOD_SEC) {
case DAC_SAWTOOTH_WAVE: index = (index + 1) % EXAMPLE_ARRAY_LEN;
ESP_ERROR_CHECK(dac_oneshot_output_voltage(chan0_handle, saw_wav[index]));
ESP_ERROR_CHECK(dac_oneshot_output_voltage(chan1_handle, saw_wav[index]));
break;
case DAC_SQUARE_WAVE:
ESP_ERROR_CHECK(dac_oneshot_output_voltage(chan0_handle, squ_wav[index]));
ESP_ERROR_CHECK(dac_oneshot_output_voltage(chan1_handle, squ_wav[index]));
break;
default:
break;
}
point_cnt++;
index++;
index %= EXAMPLE_ARRAY_LEN;
} else { } else {
wav_sel++;
wav_sel %= DAC_WAVE_MAX;
point_cnt = 0; point_cnt = 0;
index = 0; index = 0;
ESP_EARLY_LOGI(TAG, "%s wave start", wav_name[wav_sel]); wave_sel = (wave_sel + 1) % DAC_WAVE_MAX;
} }
return false; return false;
} }
@@ -83,16 +50,16 @@ void example_dac_continuous_by_timer(void)
.resolution_hz = EXAMPLE_TIMER_RESOLUTION, // 1MHz, 1 tick = 1us .resolution_hz = EXAMPLE_TIMER_RESOLUTION, // 1MHz, 1 tick = 1us
}; };
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer)); ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
dac_oneshot_config_t dac0_cfg = {
dac_oneshot_handle_t dac_handle = NULL;
dac_oneshot_config_t dac_cfg = {
.chan_id = DAC_CHAN_0, .chan_id = DAC_CHAN_0,
}; };
ESP_ERROR_CHECK(dac_oneshot_new_channel(&dac0_cfg, &chan0_handle)); ESP_ERROR_CHECK(dac_oneshot_new_channel(&dac_cfg, &dac_handle));
dac_oneshot_config_t dac1_cfg = {
.chan_id = DAC_CHAN_1,
};
ESP_ERROR_CHECK(dac_oneshot_new_channel(&dac1_cfg, &chan1_handle));
example_log_info(EXAMPLE_CONVERT_FREQ_HZ, EXAMPLE_WAVE_FREQ_HZ); example_generate_wave(dac_oneshot_get_bitwidth(dac_handle));
example_log_info(EXAMPLE_UPDATE_RATE_HZ, EXAMPLE_WAVE_FREQ_HZ);
gptimer_alarm_config_t alarm_config = { gptimer_alarm_config_t alarm_config = {
.reload_count = 0, .reload_count = 0,
@@ -102,7 +69,7 @@ void example_dac_continuous_by_timer(void)
gptimer_event_callbacks_t cbs = { gptimer_event_callbacks_t cbs = {
.on_alarm = on_timer_alarm_cb, .on_alarm = on_timer_alarm_cb,
}; };
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, NULL)); ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, dac_handle));
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config)); ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
ESP_ERROR_CHECK(gptimer_enable(gptimer)); ESP_ERROR_CHECK(gptimer_enable(gptimer));
ESP_ERROR_CHECK(gptimer_start(gptimer)); ESP_ERROR_CHECK(gptimer_start(gptimer));

View File

@@ -5,20 +5,12 @@ from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize from pytest_embedded_idf.utils import idf_parametrize
def test_dac_continuous_output(dut: Dut, mode: str, chan0_io: str, chan1_io: str) -> None: def test_dac_continuous_output(dut: Dut, mode: str) -> None:
dut.expect('dac continuous: --------------------------------------------------', timeout=10) dut.expect(f'signal_generator: DAC continuous output by {mode}', timeout=10)
dut.expect(f'dac continuous: DAC continuous output by {mode}', timeout=10) dut.expect(r'signal_generator: sine wave start', timeout=20)
dut.expect(f'dac continuous: DAC channel 0 io: GPIO_NUM_{chan0_io}', timeout=10) dut.expect(r'signal_generator: triangle wave start', timeout=20)
dut.expect(f'dac continuous: DAC channel 1 io: GPIO_NUM_{chan1_io}', timeout=10) dut.expect(r'signal_generator: sawtooth wave start', timeout=20)
dut.expect('dac continuous: Waveform: SINE -> TRIANGLE -> SAWTOOTH -> SQUARE', timeout=10) dut.expect(r'signal_generator: square wave start', timeout=20)
dut.expect('dac continuous: DAC conversion frequency \\(Hz\\): ([0-9]+)', timeout=10)
dut.expect('dac continuous: DAC wave frequency \\(Hz\\): ([0-9]+)', timeout=10)
dut.expect('dac continuous: --------------------------------------------------', timeout=10)
dut.expect(r'DAC channel 0 value:( +)(\d+)(.*)DAC channel 1 value:( +)(\d+)', timeout=10)
dut.expect(rf'dac continuous\({mode}\): sine wave start', timeout=20)
dut.expect(rf'dac continuous\({mode}\): triangle wave start', timeout=20)
dut.expect(rf'dac continuous\({mode}\): sawtooth wave start', timeout=20)
dut.expect(rf'dac continuous\({mode}\): square wave start', timeout=20)
@pytest.mark.generic @pytest.mark.generic
@@ -31,15 +23,9 @@ def test_dac_continuous_output(dut: Dut, mode: str, chan0_io: str, chan1_io: str
indirect=True, indirect=True,
) )
@idf_parametrize('target', ['esp32', 'esp32s2'], indirect=['target']) @idf_parametrize('target', ['esp32', 'esp32s2'], indirect=['target'])
def test_dac_continuous_example_with_dma(dut: Dut) -> None: def test_dac_continuous_example(dut: Dut) -> None:
sdkconfig = dut.app.sdkconfig sdkconfig = dut.app.sdkconfig
if dut.target == 'esp32': if sdkconfig['EXAMPLE_DAC_CONTINUOUS_BY_DMA']:
if sdkconfig['EXAMPLE_DAC_CONTINUOUS_BY_DMA']: test_dac_continuous_output(dut, 'DMA')
test_dac_continuous_output(dut, 'DMA', '25', '26') else:
else: test_dac_continuous_output(dut, 'timer')
test_dac_continuous_output(dut, 'timer', '25', '26')
elif dut.target == 'esp32s2':
if sdkconfig['EXAMPLE_DAC_CONTINUOUS_BY_DMA']:
test_dac_continuous_output(dut, 'DMA', '17', '18')
else:
test_dac_continuous_output(dut, 'timer', '17', '18')

View File

@@ -1,7 +1,7 @@
| Supported Targets | ESP32 | ESP32-S2 | | Supported Targets | ESP32 | ESP32-S2 |
| ----------------- | ----- | -------- | | ----------------- | ----- | -------- |
# DAC Constant Example # DAC Cosine Wave Example
(See the README.md file in the upper level 'examples' directory for more information about examples.) (See the README.md file in the upper level 'examples' directory for more information about examples.)
@@ -9,20 +9,19 @@
This example shows the basic usage of outputting cosine wave by the DAC driver. The cosine wave is generated by the hardware cosine wave generator in the DAC module. This example shows the basic usage of outputting cosine wave by the DAC driver. The cosine wave is generated by the hardware cosine wave generator in the DAC module.
This example will output cosine wave on both channels. This example outputs cosine waves on both channels, with opposite phase and different amplitude.
## How to use the Example ## How to use the Example
### Hardware Required ### Hardware Required
* A development board with ESP32 or ESP32-S2 SoC * A development board with ESP32 or ESP32-S2 SoC
- Note that some ESP32-S2 DevKits have LED on it which is connected to GPIO18 (same pin as DAC channel1), so the output voltage of DAC channel 1 can't go down due the this LED. * DAC channel to GPIO mapping: see [GPIO Summary](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/gpio.html#gpio-summary) (switch the chip target on the documentation page if needed)
* Note that some ESP32-S2 DevKits have an LED on the DAC1 pin, so that channel's output may not go fully low
* (Optional) An oscilloscope to monitor the output wave * (Optional) An oscilloscope to monitor the output wave
### Build and Flash ### Build and Flash
Note that as we use the ADC to monitor the output data, we need to set false to `CONFIG_ADC_DISABLE_DAC_OUTPUT` in the menuconfig, otherwise the ADC will shutdown the DAC power to guarantee it won't be affect by DAC.
Build the project and flash it to the board, then run monitor tool to view serial output: Build the project and flash it to the board, then run monitor tool to view serial output:
``` ```
@@ -37,29 +36,8 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui
## Example Output ## Example Output
The DAC channels can be read by ADC channels internally. The ADC read period is 100 ms, the following log is the raw ADC value read from the DAC channels. But since the ADC sample-rate might be lower than the DAC cosine period, the sampling value can only indicate that the voltage is changing.
``` ```
DAC channel 0 value: 647 DAC channel 1 value: 1728 DAC cosine wave started
DAC channel 0 value: 2112 DAC channel 1 value: 2166
DAC channel 0 value: 778 DAC channel 1 value: 2483
DAC channel 0 value: 4095 DAC channel 1 value: 1922
DAC channel 0 value: 238 DAC channel 1 value: 1282
DAC channel 0 value: 3187 DAC channel 1 value: 2609
DAC channel 0 value: 627 DAC channel 1 value: 1068
DAC channel 0 value: 3168 DAC channel 1 value: 2624
DAC channel 0 value: 225 DAC channel 1 value: 1286
DAC channel 0 value: 4095 DAC channel 1 value: 2083
DAC channel 0 value: 89 DAC channel 1 value: 1934
DAC channel 0 value: 3603 DAC channel 1 value: 1434
DAC channel 0 value: 725 DAC channel 1 value: 2469
DAC channel 0 value: 2277 DAC channel 1 value: 960
DAC channel 0 value: 1306 DAC channel 1 value: 2670
DAC channel 0 value: 1670 DAC channel 1 value: 899
DAC channel 0 value: 3189 DAC channel 1 value: 2609
DAC channel 0 value: 86 DAC channel 1 value: 1459
DAC channel 0 value: 4095 DAC channel 1 value: 2258
...
``` ```
If monitoring the DAC channels with an oscilloscope, there will be two cosine waves with opposite phase and different amplitude at 8000 Hz on the two DAC channels. Connect an oscilloscope to the DAC GPIOs to observe two cosine waves with opposite phase and different amplitude at 8000 Hz.

View File

@@ -1,3 +1,3 @@
idf_component_register(SRCS "dac_cosine_example_main.c" idf_component_register(SRCS "dac_cosine_example_main.c"
INCLUDE_DIRS "." INCLUDE_DIRS "."
PRIV_REQUIRES esp_driver_dac esp_adc) PRIV_REQUIRES esp_driver_dac)

View File

@@ -1,41 +1,13 @@
/* /*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: CC0-1.0 * SPDX-License-Identifier: CC0-1.0
*/ */
#include "freertos/FreeRTOS.h" #include <stdio.h>
#include "freertos/task.h"
#include "driver/dac_cosine.h" #include "driver/dac_cosine.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_check.h" #include "esp_check.h"
#if CONFIG_IDF_TARGET_ESP32
#define EXAMPLE_DAC_CHAN0_ADC_CHAN ADC_CHANNEL_8 // GPIO25, same as DAC channel 0
#define EXAMPLE_DAC_CHAN1_ADC_CHAN ADC_CHANNEL_9 // GPIO26, same as DAC channel 1
#define EXAMPLE_ADC_WIDTH ADC_WIDTH_BIT_12
#elif CONFIG_IDF_TARGET_ESP32S2
#define EXAMPLE_DAC_CHAN0_ADC_CHAN ADC_CHANNEL_6 // GPIO17, same as DAC channel 0
#define EXAMPLE_DAC_CHAN1_ADC_CHAN ADC_CHANNEL_7 // GPIO18, same as DAC channel 1
#define EXAMPLE_ADC_WIDTH ADC_WIDTH_BIT_13
#endif
#define EXAMPLE_ADC_ATTEN ADC_ATTEN_DB_12
static void adc_monitor_task(void *args)
{
/* Set the ADC2 channels, these channels are connected to the DAC channels internally */
adc_oneshot_unit_handle_t adc2_handle = (adc_oneshot_unit_handle_t)args;
int chan0_val = 0;
int chan1_val = 0;
while (1) {
/* Read the DAC output voltage */
ESP_ERROR_CHECK(adc_oneshot_read(adc2_handle, EXAMPLE_DAC_CHAN0_ADC_CHAN, &chan0_val));
ESP_ERROR_CHECK(adc_oneshot_read(adc2_handle, EXAMPLE_DAC_CHAN1_ADC_CHAN, &chan1_val));
printf("DAC channel 0 value: %4d\tDAC channel 1 value: %4d\n", chan0_val, chan1_val);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void app_main(void) void app_main(void)
{ {
dac_cosine_handle_t chan0_handle; dac_cosine_handle_t chan0_handle;
@@ -62,18 +34,5 @@ void app_main(void)
ESP_ERROR_CHECK(dac_cosine_start(chan0_handle)); ESP_ERROR_CHECK(dac_cosine_start(chan0_handle));
ESP_ERROR_CHECK(dac_cosine_start(chan1_handle)); ESP_ERROR_CHECK(dac_cosine_start(chan1_handle));
/* Set the ADC2 channels, these channels are connected to the DAC channels internally */ printf("DAC cosine wave started\n");
adc_oneshot_unit_handle_t adc2_handle;
adc_oneshot_unit_init_cfg_t adc_cfg = {
.unit_id = ADC_UNIT_2,
.ulp_mode = false,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&adc_cfg, &adc2_handle));
adc_oneshot_chan_cfg_t chan_cfg = {
.atten = EXAMPLE_ADC_ATTEN,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc2_handle, EXAMPLE_DAC_CHAN0_ADC_CHAN, &chan_cfg));
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc2_handle, EXAMPLE_DAC_CHAN1_ADC_CHAN, &chan_cfg));
xTaskCreate(adc_monitor_task, "adc_monitor_task", 4096, adc2_handle, 5, NULL);
} }

View File

@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0 # SPDX-License-Identifier: CC0-1.0
import pytest import pytest
from pytest_embedded import Dut from pytest_embedded import Dut
@@ -6,28 +6,6 @@ from pytest_embedded_idf.utils import idf_parametrize
@pytest.mark.generic @pytest.mark.generic
@idf_parametrize('target', ['esp32'], indirect=['target']) @idf_parametrize('target', ['esp32', 'esp32s2'], indirect=['target'])
def test_dac_cosine_wave_example_with_12bit_adc(dut: Dut) -> None: def test_dac_cosine_wave_example(dut: Dut) -> None:
res = [] dut.expect_exact('DAC cosine wave started', timeout=10)
for _ in range(30):
res.append(dut.expect(r'DAC channel 0 value:( +)(\d+)(.*)DAC channel 1 value:( +)(\d+)', timeout=10))
chan0_val = []
for val in res:
chan0_val.append(int(val.group(2)))
assert max(chan0_val) - min(chan0_val) > 1000
@pytest.mark.generic
@idf_parametrize('target', ['esp32s2'], indirect=['target'])
def test_dac_cosine_wave_example_with_13bit_adc(dut: Dut) -> None:
res = []
for _ in range(30):
res.append(dut.expect(r'DAC channel 0 value:( +)(\d+)(.*)DAC channel 1 value:( +)(\d+)', timeout=10))
chan0_val = []
for val in res:
chan0_val.append(int(val.group(2)))
assert max(chan0_val) - min(chan0_val) > 3000

View File

@@ -9,20 +9,19 @@
This example shows the oneshot usage of outputting a voltage directly by the DAC driver. This example shows the oneshot usage of outputting a voltage directly by the DAC driver.
The output voltage will increase a step every 500 ms, and it will reset to 0 periodically. Both DAC channels ramp the digital code by one eighth of the full-scale range every 500 ms, and wrap back to 0 periodically. Channel 1 starts 500 ms later than channel 0 so the two outputs are staggered.
## How to use the Example ## How to use the Example
### Hardware Required ### Hardware Required
* A development board with ESP32 or ESP32-S2 SoC * A development board with ESP32 or ESP32-S2 SoC
- Note that some ESP32-S2 DevKits have LED on it which is connected to GPIO18 (same pin as DAC channel1), so the output voltage of DAC channel 1 can't go down due the this LED. * DAC channel to GPIO mapping: see [GPIO Summary](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/gpio.html#gpio-summary) (switch the chip target on the documentation page if needed)
* (Optional) An oscilloscope to monitor the output wave * Note that some ESP32-S2 DevKits have an LED on the DAC channel 1 pin, so that channel's output may not go fully low
* (Optional) An oscilloscope to monitor the output voltage
### Build and Flash ### Build and Flash
Note that as we use the ADC to monitor the output data, we need to set false to `CONFIG_ADC_DISABLE_DAC_OUTPUT` in the menuconfig, otherwise the ADC will shutdown the DAC power to guarantee it won't be affect by DAC.
Build the project and flash it to the board, then run monitor tool to view serial output: Build the project and flash it to the board, then run monitor tool to view serial output:
``` ```
@@ -37,30 +36,16 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui
## Example Output ## Example Output
The DAC channels can be read by ADC channels internally. The ADC read period is 100 ms, the following log is the raw ADC value read from the DAC channels, it shows the output voltage is increasing every 500 ms. The example prints the digital code written by each channel. On ESP32 / ESP32-S2 the step is 32:
``` ```
DAC channel 0 value: 37 DAC channel 1 value: 0 DAC oneshot example started
DAC channel 0 value: 37 DAC channel 1 value: 0 dac_chan0 = 0
DAC channel 0 value: 38 DAC channel 1 value: 0 dac_chan0 = 32
DAC channel 0 value: 38 DAC channel 1 value: 0 dac_chan1 = 0
DAC channel 0 value: 34 DAC channel 1 value: 0 dac_chan0 = 64
DAC channel 0 value: 179 DAC channel 1 value: 117 dac_chan1 = 32
DAC channel 0 value: 176 DAC channel 1 value: 117
DAC channel 0 value: 178 DAC channel 1 value: 122
DAC channel 0 value: 179 DAC channel 1 value: 118
DAC channel 0 value: 177 DAC channel 1 value: 115
DAC channel 0 value: 316 DAC channel 1 value: 261
DAC channel 0 value: 317 DAC channel 1 value: 263
DAC channel 0 value: 311 DAC channel 1 value: 261
DAC channel 0 value: 317 DAC channel 1 value: 260
DAC channel 0 value: 317 DAC channel 1 value: 262
DAC channel 0 value: 458 DAC channel 1 value: 406
DAC channel 0 value: 456 DAC channel 1 value: 406
DAC channel 0 value: 454 DAC channel 1 value: 403
DAC channel 0 value: 457 DAC channel 1 value: 406
DAC channel 0 value: 459 DAC channel 1 value: 407
... ...
``` ```
If monitoring the DAC channels with an oscilloscope, there will be a direct voltage on the screen and it will be updated every 500 ms. If monitoring the DAC channels with an oscilloscope, there will be two stepped voltages with a one-period (500 ms) time offset.

View File

@@ -1,3 +1,3 @@
idf_component_register(SRCS "dac_oneshot_example_main.c" idf_component_register(SRCS "dac_oneshot_example_main.c"
INCLUDE_DIRS "." INCLUDE_DIRS "."
PRIV_REQUIRES esp_driver_dac esp_adc) PRIV_REQUIRES esp_driver_dac)

View File

@@ -1,55 +1,32 @@
/* /*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: CC0-1.0 * SPDX-License-Identifier: CC0-1.0
*/ */
#include <stdio.h>
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "driver/dac_oneshot.h" #include "driver/dac_oneshot.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_check.h" #include "esp_check.h"
#if CONFIG_IDF_TARGET_ESP32
#define EXAMPLE_DAC_CHAN0_ADC_CHAN ADC_CHANNEL_8 // GPIO25, same as DAC channel 0
#define EXAMPLE_DAC_CHAN1_ADC_CHAN ADC_CHANNEL_9 // GPIO26, same as DAC channel 1
#define EXAMPLE_ADC_WIDTH ADC_WIDTH_BIT_12
#elif CONFIG_IDF_TARGET_ESP32S2
#define EXAMPLE_DAC_CHAN0_ADC_CHAN ADC_CHANNEL_6 // GPIO17, same as DAC channel 0
#define EXAMPLE_DAC_CHAN1_ADC_CHAN ADC_CHANNEL_7 // GPIO18, same as DAC channel 1
#define EXAMPLE_ADC_WIDTH ADC_WIDTH_BIT_13
#endif
#define EXAMPLE_ADC_ATTEN ADC_ATTEN_DB_12
static void adc_monitor_task(void *args)
{
adc_oneshot_unit_handle_t adc2_handle = (adc_oneshot_unit_handle_t)args;
int chan0_val = 0;
int chan1_val = 0;
while (1) {
/* Read the DAC output voltage */
ESP_ERROR_CHECK(adc_oneshot_read(adc2_handle, EXAMPLE_DAC_CHAN0_ADC_CHAN, &chan0_val));
ESP_ERROR_CHECK(adc_oneshot_read(adc2_handle, EXAMPLE_DAC_CHAN1_ADC_CHAN, &chan1_val));
printf("DAC channel 0 value: %4d\tDAC channel 1 value: %4d\n", chan0_val, chan1_val);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
static void dac_output_task(void *args) static void dac_output_task(void *args)
{ {
dac_oneshot_handle_t handle = (dac_oneshot_handle_t)args; dac_oneshot_handle_t handle = args;
uint32_t val = 0; const uint16_t full_scale = 1U << dac_oneshot_get_bitwidth(handle);
uint16_t val = 0;
while (1) { while (1) {
/* Set the voltage every 100 ms */
ESP_ERROR_CHECK(dac_oneshot_output_voltage(handle, val)); ESP_ERROR_CHECK(dac_oneshot_output_voltage(handle, val));
val += 10; printf("%s = %u\n", pcTaskGetName(NULL), val);
val %= 250; val = (val + full_scale / 8) % full_scale;
vTaskDelay(pdMS_TO_TICKS(500)); vTaskDelay(pdMS_TO_TICKS(500));
} }
} }
void app_main(void) void app_main(void)
{ {
printf("DAC oneshot example started\n");
/* DAC oneshot init */ /* DAC oneshot init */
dac_oneshot_handle_t chan0_handle; dac_oneshot_handle_t chan0_handle;
dac_oneshot_config_t chan0_cfg = { dac_oneshot_config_t chan0_cfg = {
@@ -64,23 +41,7 @@ void app_main(void)
ESP_ERROR_CHECK(dac_oneshot_new_channel(&chan1_cfg, &chan1_handle)); ESP_ERROR_CHECK(dac_oneshot_new_channel(&chan1_cfg, &chan1_handle));
/* DAC oneshot outputting threads */ /* DAC oneshot outputting threads */
xTaskCreate(dac_output_task, "dac_chan0_output_task", 4096, chan0_handle, 5, NULL); xTaskCreate(dac_output_task, "dac_chan0", 4096, chan0_handle, 5, NULL);
vTaskDelay(pdMS_TO_TICKS(500)); // To differential the output of two channels vTaskDelay(pdMS_TO_TICKS(500)); // To differential the output of two channels
xTaskCreate(dac_output_task, "dac_chan1_output_task", 4096, chan1_handle, 5, NULL); xTaskCreate(dac_output_task, "dac_chan1", 4096, chan1_handle, 5, NULL);
/* ADC init, these channels are connected to the DAC channels internally */
adc_oneshot_unit_handle_t adc2_handle;
adc_oneshot_unit_init_cfg_t adc_cfg = {
.unit_id = ADC_UNIT_2,
.ulp_mode = false,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&adc_cfg, &adc2_handle));
adc_oneshot_chan_cfg_t chan_cfg = {
.atten = EXAMPLE_ADC_ATTEN,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc2_handle, EXAMPLE_DAC_CHAN0_ADC_CHAN, &chan_cfg));
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc2_handle, EXAMPLE_DAC_CHAN1_ADC_CHAN, &chan_cfg));
/* ADC monitor thread */
xTaskCreate(adc_monitor_task, "adc_monitor_task", 4096, adc2_handle, 5, NULL);
} }

View File

@@ -8,16 +8,9 @@ from pytest_embedded_idf.utils import idf_parametrize
@pytest.mark.generic @pytest.mark.generic
@idf_parametrize('target', ['esp32', 'esp32s2'], indirect=['target']) @idf_parametrize('target', ['esp32', 'esp32s2'], indirect=['target'])
def test_dac_oneshot_example(dut: Dut) -> None: def test_dac_oneshot_example(dut: Dut) -> None:
res = [] dut.expect_exact('DAC oneshot example started', timeout=10)
for _ in range(10): dut.expect_exact('dac_chan0 = 0', timeout=10)
res.append(dut.expect(r'DAC channel 0 value:( +)(\d+)(.*)DAC channel 1 value:( +)(\d+)', timeout=10)) dut.expect_exact('dac_chan1 = 0', timeout=10)
# next round
avg1_ch0 = sum(int(val.group(2)) for val in res[0:5]) / 5 dut.expect_exact('dac_chan0 = 0', timeout=10)
avg1_ch1 = sum(int(val.group(5)) for val in res[0:5]) / 5 dut.expect_exact('dac_chan1 = 0', timeout=10)
avg2_ch0 = sum(int(val.group(2)) for val in res[5:10]) / 5
avg2_ch1 = sum(int(val.group(5)) for val in res[5:10]) / 5
assert avg2_ch0 > avg1_ch0
# On ESP32-S2 CI runners, GPIO18 (DAC ch1) has an LED attached. The voltage is clamped.
if dut.target != 'esp32s2':
assert avg2_ch1 > avg1_ch1

View File

@@ -147,7 +147,6 @@ components_not_formatted_permanent:
- "/components/soc/*/register/hw_ver*/soc/" - "/components/soc/*/register/hw_ver*/soc/"
# Example resource files (generated) # Example resource files (generated)
- "/examples/peripherals/lcd/i80_controller/main/images/" - "/examples/peripherals/lcd/i80_controller/main/images/"
- "/examples/peripherals/dac/dac_continuous/dac_audio/main/audio_example_file.h"
# Coredump (generated) # Coredump (generated)
- /components/espcoredump/include_core_dump/elf.h - /components/espcoredump/include_core_dump/elf.h
# OpenOCD stub binaries (generated) # OpenOCD stub binaries (generated)