Skip to content

Commit 5574ed8

Browse files
committed
feat(usb_device_uac): add I2S example and volume dB callback
- Add set_volume_db_cb callback for decibel-based volume control - Add usb_uac_i2s example with PDM microphone and PCM amplifier support - Update documentation with new callback usage
1 parent 6563380 commit 5574ed8

File tree

12 files changed

+338
-9
lines changed

12 files changed

+338
-9
lines changed

components/usb/usb_device_uac/include/usb_device_uac.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ typedef esp_err_t (*uac_output_cb_t)(uint8_t *buf, size_t len, void *cb_ctx);
1818
typedef esp_err_t (*uac_input_cb_t)(uint8_t *buf, size_t len, size_t *bytes_read, void *cb_ctx);
1919
typedef void (*uac_set_mute_cb_t)(uint32_t mute, void *cb_ctx);
2020
typedef void (*uac_set_volume_cb_t)(uint32_t volume, void *cb_ctx);
21+
typedef void (*uac_set_volume_db_cb_t)(int32_t volume_db, void *cb_ctx);
22+
2123

2224
/**
2325
* @brief USB UAC Device Config
@@ -29,6 +31,7 @@ typedef struct {
2931
uac_input_cb_t input_cb; /*!< callback function for UAC data input, if NULL, input will be disabled */
3032
uac_set_mute_cb_t set_mute_cb; /*!< callback function for set mute, if NULL, the set mute request will be ignored */
3133
uac_set_volume_cb_t set_volume_cb; /*!< callback function for set volume, if NULL, the set volume request will be ignored */
34+
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 */
3235
void *cb_ctx; /*!< callback context, for user specific usage */
3336
#if CONFIG_USB_DEVICE_UAC_AS_PART
3437
int spk_itf_num; /*!< If CONFIG_USB_DEVICE_UAC_AS_PART is enabled, you need to provide the speaker interface number */

components/usb/usb_device_uac/usb_device_uac.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,6 @@ static bool tud_audio_feature_unit_set_request(uint8_t rhport, audio_control_req
248248
if (s_uac_device->user_cfg.set_mute_cb) {
249249
s_uac_device->user_cfg.set_mute_cb(s_uac_device->mute[request->bChannelNumber], s_uac_device->user_cfg.cb_ctx);
250250
}
251-
252251
return true;
253252
} else if (request->bControlSelector == AUDIO_FU_CTRL_VOLUME) {
254253
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
259258
if (s_uac_device->user_cfg.set_volume_cb) {
260259
s_uac_device->user_cfg.set_volume_cb(volume, s_uac_device->user_cfg.cb_ctx);
261260
}
261+
if (s_uac_device->user_cfg.set_volume_db_cb) {
262+
s_uac_device->user_cfg.set_volume_db_cb(volume_db, s_uac_device->user_cfg.cb_ctx);
263+
}
262264
return true;
263265
} else {
264266
TU_LOG1("Feature unit set request not supported, entity = %u, selector = %u, request = %u\r\n",

docs/en/usb/usb_device/usb_device_uac.rst

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@ USB Device UAC API Reference
3535
.. code:: c
3636
3737
uac_device_config_t config = {
38-
.output_cb = uac_device_output_cb, // Speaker output callback
39-
.input_cb = uac_device_input_cb, // Microphone input callback
40-
.set_mute_cb = uac_device_set_mute_cb, // Set mute callback
41-
.set_volume_cb = uac_device_set_volume_cb, // Set volume callback
38+
.output_cb = uac_device_output_cb, // Speaker output callback
39+
.input_cb = uac_device_input_cb, // Microphone input callback
40+
.set_mute_cb = uac_device_set_mute_cb, // Set mute callback
41+
.set_volume_cb = uac_device_set_volume_cb, // Set volume callback
42+
.set_volume_db_cb = uac_device_set_volume_db_cb, // Set volume in dB callback
4243
.cb_ctx = NULL,
4344
};
4445
uac_device_init(&config);

docs/zh_CN/usb/usb_device/usb_device_uac.rst

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@ USB Device UAC API 参考
3535
.. code:: c
3636
3737
uac_device_config_t config = {
38-
.output_cb = uac_device_output_cb, // Speaker output callback
39-
.input_cb = uac_device_input_cb, // Microphone input callback
40-
.set_mute_cb = uac_device_set_mute_cb, // Set mute callback
41-
.set_volume_cb = uac_device_set_volume_cb, // Set volume callback
38+
.output_cb = uac_device_output_cb, // Speaker output callback
39+
.input_cb = uac_device_input_cb, // Microphone input callback
40+
.set_mute_cb = uac_device_set_mute_cb, // Set mute callback
41+
.set_volume_cb = uac_device_set_volume_cb, // Set volume callback
42+
.set_volume_db_cb = uac_device_set_volume_db_cb, // Set volume in dB callback
4243
.cb_ctx = NULL,
4344
};
4445
uac_device_init(&config);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# The following lines of boilerplate have to be in your project's
2+
# CMakeLists in this exact order for cmake to work correctly
3+
cmake_minimum_required(VERSION 3.5)
4+
5+
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
6+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
7+
8+
add_compile_options(-Wno-ignored-qualifiers)
9+
10+
project(usb_uac_i2s)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
## USB UAC I2S Example
2+
3+
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
4+
5+
1. Supports 2 channels of analog microphone output.
6+
2. Supports 2 channels of speaker input.
7+
3. Supports volume control and mute function.
8+
9+
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.
10+
11+
## How to build the example
12+
13+
### Hardware Required
14+
15+
For ESP32-S3
16+
17+
* An ESP32-S3 development board
18+
* At least one USB Type-C cable for Power supply, programming and USB communication.
19+
* An I2S PDM microphone
20+
* An I2S PCM amplifier
21+
22+
### UAC Device Configuration
23+
24+
1. Using `idf.py menuconfig`, through `USB Device UAC` users can configure the num of SPK and MIC channels.
25+
26+
Note: This example supports one channel of microphone input.
27+
28+
### Example Output
29+
30+
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.
31+
32+
### Build and Flash
33+
34+
1. Make sure `ESP-IDF` is setup successfully
35+
36+
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:
37+
38+
```bash
39+
. $HOME/esp/esp-idf/export.sh
40+
```
41+
42+
3. Set ESP-IDF build target to `esp32s3` or `esp32-p4`
43+
44+
```bash
45+
idf.py set-target esp32s3
46+
```
47+
48+
4. Build, Flash, output log
49+
50+
```bash
51+
idf.py build flash monitor
52+
```
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
idf_component_register(SRC_DIRS "."
2+
INCLUDE_DIRS ".")
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
menu "Example Configuration"
2+
3+
menu "Speaker I2S Pin Configuration"
4+
5+
config SPEAKER_I2S_DOUT
6+
int "Speaker I2S Data Out Pin"
7+
default 13
8+
help
9+
GPIO pin for I2S speaker data out (DOUT).
10+
11+
config SPEAKER_I2S_BCLK
12+
int "Speaker I2S Bit Clock Pin"
13+
default 14
14+
help
15+
GPIO pin for I2S speaker bit clock (BCLK).
16+
17+
config SPEAKER_I2S_LRC
18+
int "Speaker I2S Left/Right Clock Pin"
19+
default 21
20+
help
21+
GPIO pin for I2S speaker word select/left-right clock (LRC/WS).
22+
23+
config SPEAKER_SD_MODE_ENABLE
24+
bool "Enable Speaker Shutdown/Mode Control Pin"
25+
default y
26+
help
27+
Enable control of speaker amplifier shutdown/mode pin.
28+
Disable this if your amplifier doesn't have a shutdown control pin.
29+
30+
config SPEAKER_SD_MODE
31+
int "Speaker Shutdown/Mode Control Pin"
32+
default 12
33+
depends on SPEAKER_SD_MODE_ENABLE
34+
help
35+
GPIO pin for speaker amplifier shutdown/mode control.
36+
37+
endmenu
38+
39+
menu "Microphone I2S Pin Configuration"
40+
41+
config MIC_I2S_CLK
42+
int "Microphone I2S Clock Pin"
43+
default 9
44+
help
45+
GPIO pin for microphone PDM clock.
46+
47+
config MIC_I2S_LR
48+
int "Microphone I2S L/R Pin"
49+
default 10
50+
help
51+
GPIO pin for microphone L/R select.
52+
53+
config MIC_I2S_DATA
54+
int "Microphone I2S Data Pin"
55+
default 11
56+
help
57+
GPIO pin for microphone PDM data input.
58+
59+
endmenu
60+
61+
endmenu
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## IDF Component Manager Manifest File
2+
version: "0.2.0"
3+
targets:
4+
- esp32s3
5+
dependencies:
6+
idf: ">=5.1"
7+
usb_device_uac:
8+
version: "1.*"
9+
override_path: "../../../../../components/usb/usb_device_uac"
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: CC0-1.0
5+
*/
6+
#include "driver/gpio.h"
7+
#include "driver/i2s_pdm.h"
8+
#include "driver/i2s_std.h"
9+
#include "driver/ledc.h"
10+
#include "freertos/FreeRTOS.h"
11+
#include "freertos/task.h"
12+
#include "sdkconfig.h"
13+
#include "usb_device_uac.h"
14+
#include <math.h>
15+
16+
#define SPEAKER_I2S_DOUT CONFIG_SPEAKER_I2S_DOUT
17+
#define SPEAKER_I2S_BCLK CONFIG_SPEAKER_I2S_BCLK
18+
#define SPEAKER_I2S_LRC CONFIG_SPEAKER_I2S_LRC
19+
#ifdef CONFIG_SPEAKER_SD_MODE_ENABLE
20+
#define SPEAKER_SD_MODE CONFIG_SPEAKER_SD_MODE
21+
#endif
22+
23+
#define MIC_I2S_CLK CONFIG_MIC_I2S_CLK
24+
#define MIC_I2S_LR CONFIG_MIC_I2S_LR
25+
#define MIC_I2S_DATA CONFIG_MIC_I2S_DATA
26+
27+
static i2s_chan_handle_t rx;
28+
static i2s_chan_handle_t tx;
29+
30+
static bool is_muted = false;
31+
/* default to 0dB (full volume) */
32+
static uint32_t gain = 100;
33+
34+
static esp_err_t usb_uac_device_output_cb(uint8_t *buf, size_t len, void *arg) {
35+
if (!tx) {
36+
return ESP_FAIL;
37+
}
38+
/* process the samples */
39+
int16_t *samples = (int16_t *)buf;
40+
for (size_t i = 0; i < len / 2; i++) {
41+
/* if muted - set the sample to 0 so no sound is played */
42+
if (is_muted) {
43+
samples[i] = 0;
44+
continue;
45+
}
46+
/* apply the gain */
47+
int32_t sample = samples[i];
48+
sample = (sample * gain) / 100;
49+
/* clip the sample to the range of -32768 to 32767 (16 bits) */
50+
if (sample > 32767) {
51+
sample = 32767;
52+
} else if (sample < -32768) {
53+
sample = -32768;
54+
}
55+
samples[i] = (int16_t)sample;
56+
}
57+
/* write the samples to the I2S TX channel */
58+
size_t total_bytes_written = 0;
59+
while (total_bytes_written < len) {
60+
size_t bytes_written = 0;
61+
i2s_channel_write(tx, (uint8_t *)buf + total_bytes_written,
62+
len - total_bytes_written, &bytes_written, portMAX_DELAY);
63+
total_bytes_written += bytes_written;
64+
}
65+
if (total_bytes_written < len) {
66+
return ESP_FAIL;
67+
}
68+
return ESP_OK;
69+
}
70+
71+
static esp_err_t usb_uac_device_input_cb(uint8_t *buf, size_t len,
72+
size_t *bytes_read, void *arg) {
73+
if (!rx) {
74+
return ESP_FAIL;
75+
}
76+
/* read directly from the I2S RX channel */
77+
return i2s_channel_read(rx, buf, len, bytes_read, portMAX_DELAY);
78+
}
79+
80+
static void usb_uac_device_set_mute_cb(uint32_t mute, void *arg) {
81+
is_muted = mute;
82+
}
83+
84+
static void usb_uac_device_set_volume_cb_db(int32_t volume_db, void *arg) {
85+
/* convert from dB to a percentage */
86+
gain = pow(10, volume_db / 20.0f) * 100.0f;
87+
}
88+
89+
static void usb_uac_device_init(void) {
90+
uac_device_config_t config = {
91+
.output_cb = usb_uac_device_output_cb,
92+
.input_cb = usb_uac_device_input_cb,
93+
.set_mute_cb = usb_uac_device_set_mute_cb,
94+
.set_volume_db_cb = usb_uac_device_set_volume_cb_db,
95+
.cb_ctx = NULL,
96+
};
97+
/* Init UAC device, UAC related configurations can be set by the menuconfig */
98+
ESP_ERROR_CHECK(uac_device_init(&config));
99+
}
100+
101+
void init_pdm_rx(void) {
102+
i2s_chan_config_t chan_cfg =
103+
I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
104+
i2s_new_channel(&chan_cfg, NULL, &rx);
105+
106+
i2s_pdm_rx_config_t pdm_cfg = {
107+
.clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(CONFIG_UAC_SAMPLE_RATE),
108+
.slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT,
109+
I2S_SLOT_MODE_MONO),
110+
.gpio_cfg =
111+
{
112+
.clk = MIC_I2S_CLK, // PDM clock
113+
// QUESTION - what about the LR clock pin? No longer relevant? Do
114+
// we tie it high or low?
115+
.din = MIC_I2S_DATA, // PDM data
116+
.invert_flags = {.clk_inv = false},
117+
},
118+
};
119+
pdm_cfg.slot_cfg.slot_mode = I2S_SLOT_MODE_MONO; // single mic
120+
121+
i2s_channel_init_pdm_rx_mode(rx, &pdm_cfg);
122+
i2s_channel_enable(rx);
123+
}
124+
125+
static void init_pcm_tx(void) {
126+
i2s_chan_config_t chan_cfg =
127+
I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_1, I2S_ROLE_MASTER);
128+
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx, NULL));
129+
130+
i2s_std_config_t std_cfg = {
131+
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(CONFIG_UAC_SAMPLE_RATE),
132+
.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT,
133+
I2S_SLOT_MODE_MONO),
134+
.gpio_cfg =
135+
{
136+
.mclk = I2S_GPIO_UNUSED, // set this if your amp needs MCLK
137+
.bclk = SPEAKER_I2S_BCLK,
138+
.ws = SPEAKER_I2S_LRC,
139+
.dout = SPEAKER_I2S_DOUT,
140+
.din = I2S_GPIO_UNUSED,
141+
.invert_flags =
142+
{
143+
.mclk_inv = false,
144+
.bclk_inv = false,
145+
.ws_inv = false, // if L/R are swapped or silent, try true
146+
},
147+
},
148+
};
149+
150+
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx, &std_cfg));
151+
ESP_ERROR_CHECK(i2s_channel_enable(tx));
152+
}
153+
154+
void app_main(void) {
155+
/* set the LR pin to low */
156+
gpio_reset_pin(MIC_I2S_LR);
157+
gpio_set_direction(MIC_I2S_LR, GPIO_MODE_OUTPUT);
158+
gpio_set_level(MIC_I2S_LR, 0);
159+
160+
#ifdef CONFIG_SPEAKER_SD_MODE_ENABLE
161+
// enable the amplifier
162+
gpio_reset_pin(SPEAKER_SD_MODE);
163+
gpio_set_direction(SPEAKER_SD_MODE, GPIO_MODE_OUTPUT);
164+
gpio_set_level(SPEAKER_SD_MODE, 1);
165+
#endif
166+
167+
// init the I2S peripherals
168+
init_pdm_rx();
169+
init_pcm_tx();
170+
171+
// init the USB audio device
172+
usb_uac_device_init();
173+
174+
// Nothing to do here - the USB audio device will take care of everything
175+
}

0 commit comments

Comments
 (0)