diff --git a/components/usb/usb_device_uac/include/usb_device_uac.h b/components/usb/usb_device_uac/include/usb_device_uac.h index 46dfa3015..db0cab296 100644 --- a/components/usb/usb_device_uac/include/usb_device_uac.h +++ b/components/usb/usb_device_uac/include/usb_device_uac.h @@ -18,6 +18,8 @@ typedef esp_err_t (*uac_output_cb_t)(uint8_t *buf, size_t len, void *cb_ctx); typedef esp_err_t (*uac_input_cb_t)(uint8_t *buf, size_t len, size_t *bytes_read, void *cb_ctx); typedef void (*uac_set_mute_cb_t)(uint32_t mute, void *cb_ctx); typedef void (*uac_set_volume_cb_t)(uint32_t volume, void *cb_ctx); +typedef void (*uac_set_volume_db_cb_t)(int32_t volume_db, void *cb_ctx); + /** * @brief USB UAC Device Config @@ -29,6 +31,7 @@ typedef struct { uac_input_cb_t input_cb; /*!< callback function for UAC data input, if NULL, input will be disabled */ uac_set_mute_cb_t set_mute_cb; /*!< callback function for set mute, if NULL, the set mute request will be ignored */ uac_set_volume_cb_t set_volume_cb; /*!< callback function for set volume, if NULL, the set volume request will be ignored */ + uac_set_volume_db_cb_t set_volume_db_cb; /*!< callback function for set volume in dB, if NULL, the set volume request will be ignored */ void *cb_ctx; /*!< callback context, for user specific usage */ #if CONFIG_USB_DEVICE_UAC_AS_PART int spk_itf_num; /*!< If CONFIG_USB_DEVICE_UAC_AS_PART is enabled, you need to provide the speaker interface number */ diff --git a/components/usb/usb_device_uac/usb_device_uac.c b/components/usb/usb_device_uac/usb_device_uac.c index a88fd8e13..48099abb7 100644 --- a/components/usb/usb_device_uac/usb_device_uac.c +++ b/components/usb/usb_device_uac/usb_device_uac.c @@ -248,7 +248,6 @@ static bool tud_audio_feature_unit_set_request(uint8_t rhport, audio_control_req if (s_uac_device->user_cfg.set_mute_cb) { s_uac_device->user_cfg.set_mute_cb(s_uac_device->mute[request->bChannelNumber], s_uac_device->user_cfg.cb_ctx); } - return true; } else if (request->bControlSelector == AUDIO_FU_CTRL_VOLUME) { TU_VERIFY(request->wLength == sizeof(audio_control_cur_2_t)); @@ -259,6 +258,9 @@ static bool tud_audio_feature_unit_set_request(uint8_t rhport, audio_control_req if (s_uac_device->user_cfg.set_volume_cb) { s_uac_device->user_cfg.set_volume_cb(volume, s_uac_device->user_cfg.cb_ctx); } + if (s_uac_device->user_cfg.set_volume_db_cb) { + s_uac_device->user_cfg.set_volume_db_cb(volume_db, s_uac_device->user_cfg.cb_ctx); + } return true; } else { TU_LOG1("Feature unit set request not supported, entity = %u, selector = %u, request = %u\r\n", @@ -501,6 +503,7 @@ esp_err_t uac_device_init(uac_device_config_t *config) s_uac_device->user_cfg.cb_ctx = config->cb_ctx; s_uac_device->user_cfg.set_mute_cb = config->set_mute_cb; s_uac_device->user_cfg.set_volume_cb = config->set_volume_cb; + s_uac_device->user_cfg.set_volume_db_cb = config->set_volume_db_cb; s_uac_device->current_sample_rate = DEFAULT_SAMPLE_RATE; s_uac_device->mic_buf_write = s_uac_device->mic_buf1; s_uac_device->mic_buf_read = s_uac_device->mic_buf2; diff --git a/docs/en/usb/usb_device/usb_device_uac.rst b/docs/en/usb/usb_device/usb_device_uac.rst index 3b5d33e9e..5dad4b775 100644 --- a/docs/en/usb/usb_device/usb_device_uac.rst +++ b/docs/en/usb/usb_device/usb_device_uac.rst @@ -35,10 +35,11 @@ USB Device UAC API Reference .. code:: c uac_device_config_t config = { - .output_cb = uac_device_output_cb, // Speaker output callback - .input_cb = uac_device_input_cb, // Microphone input callback - .set_mute_cb = uac_device_set_mute_cb, // Set mute callback - .set_volume_cb = uac_device_set_volume_cb, // Set volume callback + .output_cb = uac_device_output_cb, // Speaker output callback + .input_cb = uac_device_input_cb, // Microphone input callback + .set_mute_cb = uac_device_set_mute_cb, // Set mute callback + .set_volume_cb = uac_device_set_volume_cb, // Set volume callback + .set_volume_db_cb = uac_device_set_volume_db_cb, // Set volume in dB callback .cb_ctx = NULL, }; uac_device_init(&config); diff --git a/docs/zh_CN/usb/usb_device/usb_device_uac.rst b/docs/zh_CN/usb/usb_device/usb_device_uac.rst index cb7322d6d..b423df4bb 100644 --- a/docs/zh_CN/usb/usb_device/usb_device_uac.rst +++ b/docs/zh_CN/usb/usb_device/usb_device_uac.rst @@ -35,10 +35,11 @@ USB Device UAC API 参考 .. code:: c uac_device_config_t config = { - .output_cb = uac_device_output_cb, // Speaker output callback - .input_cb = uac_device_input_cb, // Microphone input callback - .set_mute_cb = uac_device_set_mute_cb, // Set mute callback - .set_volume_cb = uac_device_set_volume_cb, // Set volume callback + .output_cb = uac_device_output_cb, // Speaker output callback + .input_cb = uac_device_input_cb, // Microphone input callback + .set_mute_cb = uac_device_set_mute_cb, // Set mute callback + .set_volume_cb = uac_device_set_volume_cb, // Set volume callback + .set_volume_db_cb = uac_device_set_volume_db_cb, // Set volume in dB callback .cb_ctx = NULL, }; uac_device_init(&config); diff --git a/examples/usb/device/usb_uac_i2s/CMakeLists.txt b/examples/usb/device/usb_uac_i2s/CMakeLists.txt new file mode 100644 index 000000000..49c57c35b --- /dev/null +++ b/examples/usb/device/usb_uac_i2s/CMakeLists.txt @@ -0,0 +1,10 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection. +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +add_compile_options(-Wno-ignored-qualifiers) + +project(usb_uac_i2s) diff --git a/examples/usb/device/usb_uac_i2s/README.md b/examples/usb/device/usb_uac_i2s/README.md new file mode 100644 index 000000000..356b9e55a --- /dev/null +++ b/examples/usb/device/usb_uac_i2s/README.md @@ -0,0 +1,52 @@ +## USB UAC I2S Example + +This example demonstrates how to utilize the USB function of ESP32-Sx to implement a UAC (USB Audio Class) device with an I2S PDM microphone and I2S PCM amplifier + +1. Supports 2 channels of analog microphone output. +2. Supports 2 channels of speaker input. +3. Supports volume control and mute function. + +For information about the [usb_device_uac](https://docs.espressif.com/projects/esp-iot-solution/zh_CN/latest/usb/usb_device/usb_device_uac.html) component. + +## How to build the example + +### Hardware Required + +For ESP32-S3 + +* An ESP32-S3 development board +* At least one USB Type-C cable for Power supply, programming and USB communication. +* An I2S PDM microphone +* An I2S PCM amplifier + +### UAC Device Configuration + +1. Using `idf.py menuconfig`, through `USB Device UAC` users can configure the num of SPK and MIC channels. + +Note: This example supports one channel of microphone input. + +### Example Output + +After the programming is completed, insert the USB interface on the development board into the computer. An audio device will be displayed. Select this device for recording or for playback. + +### Build and Flash + +1. Make sure `ESP-IDF` is setup successfully + +2. Set up the `ESP-IDF` environment variables, please refer [Set up the environment variables](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html#step-4-set-up-the-environment-variables), Linux can use: + + ```bash + . $HOME/esp/esp-idf/export.sh + ``` + +3. Set ESP-IDF build target to `esp32s3` or `esp32-p4` + + ```bash + idf.py set-target esp32s3 + ``` + +4. Build, Flash, output log + + ```bash + idf.py build flash monitor + ``` diff --git a/examples/usb/device/usb_uac_i2s/main/CMakeLists.txt b/examples/usb/device/usb_uac_i2s/main/CMakeLists.txt new file mode 100644 index 000000000..a941e22ba --- /dev/null +++ b/examples/usb/device/usb_uac_i2s/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRC_DIRS "." + INCLUDE_DIRS ".") diff --git a/examples/usb/device/usb_uac_i2s/main/Kconfig.projbuild b/examples/usb/device/usb_uac_i2s/main/Kconfig.projbuild new file mode 100644 index 000000000..689b0a90e --- /dev/null +++ b/examples/usb/device/usb_uac_i2s/main/Kconfig.projbuild @@ -0,0 +1,61 @@ +menu "Example Configuration" + + menu "Speaker I2S Pin Configuration" + + config SPEAKER_I2S_DOUT + int "Speaker I2S Data Out Pin" + default 13 + help + GPIO pin for I2S speaker data out (DOUT). + + config SPEAKER_I2S_BCLK + int "Speaker I2S Bit Clock Pin" + default 14 + help + GPIO pin for I2S speaker bit clock (BCLK). + + config SPEAKER_I2S_LRC + int "Speaker I2S Left/Right Clock Pin" + default 21 + help + GPIO pin for I2S speaker word select/left-right clock (LRC/WS). + + config SPEAKER_SD_MODE_ENABLE + bool "Enable Speaker Shutdown/Mode Control Pin" + default y + help + Enable control of speaker amplifier shutdown/mode pin. + Disable this if your amplifier doesn't have a shutdown control pin. + + config SPEAKER_SD_MODE + int "Speaker Shutdown/Mode Control Pin" + default 12 + depends on SPEAKER_SD_MODE_ENABLE + help + GPIO pin for speaker amplifier shutdown/mode control. + + endmenu + + menu "Microphone I2S Pin Configuration" + + config MIC_I2S_CLK + int "Microphone I2S Clock Pin" + default 9 + help + GPIO pin for microphone PDM clock. + + config MIC_I2S_LR + int "Microphone I2S L/R Pin" + default 10 + help + GPIO pin for microphone L/R select. + + config MIC_I2S_DATA + int "Microphone I2S Data Pin" + default 11 + help + GPIO pin for microphone PDM data input. + + endmenu + +endmenu diff --git a/examples/usb/device/usb_uac_i2s/main/idf_component.yml b/examples/usb/device/usb_uac_i2s/main/idf_component.yml new file mode 100644 index 000000000..556234ed1 --- /dev/null +++ b/examples/usb/device/usb_uac_i2s/main/idf_component.yml @@ -0,0 +1,9 @@ +## IDF Component Manager Manifest File +version: "0.2.0" +targets: + - esp32s3 +dependencies: + idf: ">=5.1" + usb_device_uac: + version: "1.*" + override_path: "../../../../../components/usb/usb_device_uac" diff --git a/examples/usb/device/usb_uac_i2s/main/usb_uac_i2s_main.c b/examples/usb/device/usb_uac_i2s/main/usb_uac_i2s_main.c new file mode 100644 index 000000000..eef482a97 --- /dev/null +++ b/examples/usb/device/usb_uac_i2s/main/usb_uac_i2s_main.c @@ -0,0 +1,175 @@ +/* + * SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include "driver/gpio.h" +#include "driver/i2s_pdm.h" +#include "driver/i2s_std.h" +#include "driver/ledc.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sdkconfig.h" +#include "usb_device_uac.h" +#include + +#define SPEAKER_I2S_DOUT CONFIG_SPEAKER_I2S_DOUT +#define SPEAKER_I2S_BCLK CONFIG_SPEAKER_I2S_BCLK +#define SPEAKER_I2S_LRC CONFIG_SPEAKER_I2S_LRC +#ifdef CONFIG_SPEAKER_SD_MODE_ENABLE +#define SPEAKER_SD_MODE CONFIG_SPEAKER_SD_MODE +#endif + +#define MIC_I2S_CLK CONFIG_MIC_I2S_CLK +#define MIC_I2S_LR CONFIG_MIC_I2S_LR +#define MIC_I2S_DATA CONFIG_MIC_I2S_DATA + +static i2s_chan_handle_t rx; +static i2s_chan_handle_t tx; + +static bool is_muted = false; +/* default to 0dB (full volume) */ +static uint32_t gain = 100; + +static esp_err_t usb_uac_device_output_cb(uint8_t *buf, size_t len, void *arg) { + if (!tx) { + return ESP_FAIL; + } + /* process the samples */ + int16_t *samples = (int16_t *)buf; + for (size_t i = 0; i < len / 2; i++) { + /* if muted - set the sample to 0 so no sound is played */ + if (is_muted) { + samples[i] = 0; + continue; + } + /* apply the gain */ + int32_t sample = samples[i]; + sample = (sample * gain) / 100; + /* clip the sample to the range of -32768 to 32767 (16 bits) */ + if (sample > 32767) { + sample = 32767; + } else if (sample < -32768) { + sample = -32768; + } + samples[i] = (int16_t)sample; + } + /* write the samples to the I2S TX channel */ + size_t total_bytes_written = 0; + while (total_bytes_written < len) { + size_t bytes_written = 0; + i2s_channel_write(tx, (uint8_t *)buf + total_bytes_written, + len - total_bytes_written, &bytes_written, portMAX_DELAY); + total_bytes_written += bytes_written; + } + if (total_bytes_written < len) { + return ESP_FAIL; + } + return ESP_OK; +} + +static esp_err_t usb_uac_device_input_cb(uint8_t *buf, size_t len, + size_t *bytes_read, void *arg) { + if (!rx) { + return ESP_FAIL; + } + /* read directly from the I2S RX channel */ + return i2s_channel_read(rx, buf, len, bytes_read, portMAX_DELAY); +} + +static void usb_uac_device_set_mute_cb(uint32_t mute, void *arg) { + is_muted = mute; +} + +static void usb_uac_device_set_volume_cb_db(int32_t volume_db, void *arg) { + /* convert from dB to a percentage */ + gain = pow(10, volume_db / 20.0f) * 100.0f; +} + +static void usb_uac_device_init(void) { + uac_device_config_t config = { + .output_cb = usb_uac_device_output_cb, + .input_cb = usb_uac_device_input_cb, + .set_mute_cb = usb_uac_device_set_mute_cb, + .set_volume_db_cb = usb_uac_device_set_volume_cb_db, + .cb_ctx = NULL, + }; + /* Init UAC device, UAC related configurations can be set by the menuconfig */ + ESP_ERROR_CHECK(uac_device_init(&config)); +} + +void init_pdm_rx(void) { + i2s_chan_config_t chan_cfg = + I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); + i2s_new_channel(&chan_cfg, NULL, &rx); + + i2s_pdm_rx_config_t pdm_cfg = { + .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(CONFIG_UAC_SAMPLE_RATE), + .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, + I2S_SLOT_MODE_MONO), + .gpio_cfg = + { + .clk = MIC_I2S_CLK, // PDM clock + // QUESTION - what about the LR clock pin? No longer relevant? Do + // we tie it high or low? + .din = MIC_I2S_DATA, // PDM data + .invert_flags = {.clk_inv = false}, + }, + }; + pdm_cfg.slot_cfg.slot_mode = I2S_SLOT_MODE_MONO; // single mic + + i2s_channel_init_pdm_rx_mode(rx, &pdm_cfg); + i2s_channel_enable(rx); +} + +static void init_pcm_tx(void) { + i2s_chan_config_t chan_cfg = + I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_1, I2S_ROLE_MASTER); + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx, NULL)); + + i2s_std_config_t std_cfg = { + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(CONFIG_UAC_SAMPLE_RATE), + .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, + I2S_SLOT_MODE_MONO), + .gpio_cfg = + { + .mclk = I2S_GPIO_UNUSED, // set this if your amp needs MCLK + .bclk = SPEAKER_I2S_BCLK, + .ws = SPEAKER_I2S_LRC, + .dout = SPEAKER_I2S_DOUT, + .din = I2S_GPIO_UNUSED, + .invert_flags = + { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, // if L/R are swapped or silent, try true + }, + }, + }; + + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx, &std_cfg)); + ESP_ERROR_CHECK(i2s_channel_enable(tx)); +} + +void app_main(void) { + /* set the LR pin to low */ + gpio_reset_pin(MIC_I2S_LR); + gpio_set_direction(MIC_I2S_LR, GPIO_MODE_OUTPUT); + gpio_set_level(MIC_I2S_LR, 0); + + #ifdef CONFIG_SPEAKER_SD_MODE_ENABLE + // enable the amplifier + gpio_reset_pin(SPEAKER_SD_MODE); + gpio_set_direction(SPEAKER_SD_MODE, GPIO_MODE_OUTPUT); + gpio_set_level(SPEAKER_SD_MODE, 1); + #endif + + // init the I2S peripherals + init_pdm_rx(); + init_pcm_tx(); + + // init the USB audio device + usb_uac_device_init(); + + // Nothing to do here - the USB audio device will take care of everything +} diff --git a/examples/usb/device/usb_uac_i2s/sdkconfig.defaults b/examples/usb/device/usb_uac_i2s/sdkconfig.defaults new file mode 100644 index 000000000..4b7934937 --- /dev/null +++ b/examples/usb/device/usb_uac_i2s/sdkconfig.defaults @@ -0,0 +1,5 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration +# +CONFIG_FREERTOS_HZ=1000 +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/examples/usb/device/usb_uac_i2s/sdkconfig.defaults.esp32s3 b/examples/usb/device/usb_uac_i2s/sdkconfig.defaults.esp32s3 new file mode 100644 index 000000000..39119f9e8 --- /dev/null +++ b/examples/usb/device/usb_uac_i2s/sdkconfig.defaults.esp32s3 @@ -0,0 +1,8 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration +# +CONFIG_UAC_SPEAKER_CHANNEL_NUM=2 +CONFIG_UAC_MIC_CHANNEL_NUM=2 +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y