|
| 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