/* SD card and FAT filesystem example. This example uses SPI peripheral to communicate with SD card. This example code is in the Public Domain (or CC0 licensed, at your option.) Unless required by applicable law or agreed to in writing, this software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ #include #include #include #include "esp_vfs_fat.h" #include "sdmmc_cmd.h" #include "esp_io_expander_tca95xx_16bit.h" #include "driver/i2c.h" #include "driver/i2s_pdm.h" #include "driver/gpio.h" #include "esp_log.h" #define EXAMPLE_PDM_TX_CLK_IO GPIO_NUM_13 // I2S PDM TX clock io number #define EXAMPLE_PDM_TX_DOUT_IO GPIO_NUM_12 // I2S PDM TX data out io number #define EXAMPLE_PDM_TX_FREQ_HZ 44100 // I2S PDM TX frequency #define EXAMPLE_WAVE_AMPLITUDE (20000.0) // 1~32767 #define CONST_PI (3.1416f) #define EXAMPLE_SINE_WAVE_LEN(tone) (uint32_t)((EXAMPLE_PDM_TX_FREQ_HZ / (float)tone) + 0.5) // The sample point number per sine wave to generate the tone #define EXAMPLE_TONE_LAST_TIME_MS 500 #define EXAMPLE_BYTE_NUM_EVERY_TONE (EXAMPLE_TONE_LAST_TIME_MS * EXAMPLE_PDM_TX_FREQ_HZ / 1000) #define EXAMPLE_MAX_CHAR_SIZE 64 static const char *TAG = "example"; #define MOUNT_POINT "/sdcard" // Pin assignments can be set in menuconfig, see "SD SPI Example Configuration" menu. // You can also change the pin assignments here by changing the following 4 lines. #define PIN_NUM_MISO CONFIG_EXAMPLE_PIN_MISO #define PIN_NUM_MOSI CONFIG_EXAMPLE_PIN_MOSI #define PIN_NUM_CLK CONFIG_EXAMPLE_PIN_CLK #define PIN_NUM_CS CONFIG_EXAMPLE_PIN_CS #define AUDIO_BUFFER 2048 i2s_chan_handle_t tx_handle = NULL; esp_io_expander_handle_t io_expander = NULL; void init_io_expander() { i2c_port_t i2c_master_port = I2C_NUM_0; i2c_config_t conf = { .mode = I2C_MODE_MASTER, .sda_io_num = 4, .scl_io_num = 5, .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_pullup_en = GPIO_PULLUP_ENABLE }; conf.master.clk_speed = 100000; i2c_param_config(i2c_master_port, &conf); i2c_driver_install(i2c_master_port, conf.mode, 0, 0, 0); esp_io_expander_new_i2c_tca95xx_16bit(I2C_NUM_0, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000, &io_expander); esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_11 | IO_EXPANDER_PIN_NUM_12 | IO_EXPANDER_PIN_NUM_13, IO_EXPANDER_OUTPUT); esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, IO_EXPANDER_OUTPUT); esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_11 | IO_EXPANDER_PIN_NUM_12 | IO_EXPANDER_PIN_NUM_13, 1); esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, 1); esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_1, 0); } esp_err_t play_wav(char *fp) { FILE *fh = fopen(fp, "rb"); if (fh == NULL) { ESP_LOGE(TAG, "Failed to open file"); return ESP_ERR_INVALID_ARG; } // skip the header... fseek(fh, 44, SEEK_SET); // create a writer buffer int16_t *buf = calloc(AUDIO_BUFFER, sizeof(int16_t)); size_t bytes_read = 0; size_t bytes_written = 0; bytes_read = fread(buf, sizeof(int16_t), AUDIO_BUFFER, fh); // i2s_channel_enable(tx_handle); while (bytes_read > 0) { // write the buffer to the i2s i2s_channel_write(tx_handle, buf, bytes_read * sizeof(int16_t), &bytes_written, portMAX_DELAY); bytes_read = fread(buf, sizeof(int16_t), AUDIO_BUFFER, fh); ESP_LOGV(TAG, "Bytes read: %d", bytes_read); } i2s_channel_disable(tx_handle); free(buf); return ESP_OK; } static i2s_chan_handle_t i2s_example_init_pdm_tx(void) { i2s_chan_handle_t tx_chan; // I2S tx channel handler /* Setp 1: Determine the I2S channel configuration and allocate TX channel only * The default configuration can be generated by the helper macro, * it only requires the I2S controller id and I2S role, * but note that PDM channel can only be registered on I2S_NUM_0 */ i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER); tx_chan_cfg.auto_clear = true; ESP_ERROR_CHECK(i2s_new_channel(&tx_chan_cfg, &tx_chan, NULL)); /* Step 2: Setting the configurations of PDM TX mode and initialize the TX channel * The slot configuration and clock configuration can be generated by the macros * These two helper macros is defined in 'i2s_pdm.h' which can only be used in PDM TX mode. * They can help to specify the slot and clock configurations for initialization or re-configuring */ i2s_pdm_tx_config_t pdm_tx_cfg = { .clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(EXAMPLE_PDM_TX_FREQ_HZ), /* The data bit-width of PDM mode is fixed to 16 */ .slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), .gpio_cfg = { .clk = EXAMPLE_PDM_TX_CLK_IO, .dout = EXAMPLE_PDM_TX_DOUT_IO, .invert_flags = { .clk_inv = false, }, }, }; ESP_ERROR_CHECK(i2s_channel_init_pdm_tx_mode(tx_chan, &pdm_tx_cfg)); /* Step 3: Enable the tx channel before writing data */ ESP_ERROR_CHECK(i2s_channel_enable(tx_chan)); return tx_chan; } void app_main(void) { init_io_expander(); esp_err_t ret; // Options for mounting the filesystem. // If format_if_mount_failed is set to true, SD card will be partitioned and // formatted in case when mounting fails. esp_vfs_fat_sdmmc_mount_config_t mount_config = { .format_if_mount_failed = true, .max_files = 5, .allocation_unit_size = 16 * 1024 }; sdmmc_card_t *card; const char mount_point[] = MOUNT_POINT; ESP_LOGI(TAG, "Initializing SD card"); // Use settings defined above to initialize SD card and mount FAT filesystem. // Note: esp_vfs_fat_sdmmc/sdspi_mount is all-in-one convenience functions. // Please check its source code and implement error recovery when developing // production applications. ESP_LOGI(TAG, "Using SPI peripheral"); // By default, SD card frequency is initialized to SDMMC_FREQ_DEFAULT (20MHz)/ // For setting a specific frequency, use host.max_freq_khz (range 400kHz - 20MHz for SDSPI) // Example: for fixed frequency of 10MHz, use host.max_freq_khz = 10000; sdmmc_host_t host = SDSPI_HOST_DEFAULT(); spi_bus_config_t bus_cfg = { .mosi_io_num = PIN_NUM_MOSI, .miso_io_num = PIN_NUM_MISO, .sclk_io_num = PIN_NUM_CLK, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = 4000, }; ret = spi_bus_initialize(host.slot, &bus_cfg, SDSPI_DEFAULT_DMA); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize bus."); return; } // This initializes the slot without card detect (CD) and write protect (WP) signals. // Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals. sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT(); slot_config.gpio_cs = PIN_NUM_CS; slot_config.host_id = host.slot; ESP_LOGI(TAG, "Mounting filesystem"); ret = esp_vfs_fat_sdspi_mount(mount_point, &host, &slot_config, &mount_config, &card); if (ret != ESP_OK) { if (ret == ESP_FAIL) { ESP_LOGE(TAG, "Failed to mount filesystem. " "If you want the card to be formatted, set the CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option."); } else { ESP_LOGE(TAG, "Failed to initialize the card (%s). " "Make sure SD card lines have pull-up resistors in place.", esp_err_to_name(ret)); } return; } ESP_LOGI(TAG, "Filesystem mounted"); // Card has been initialized, print its properties sdmmc_card_print_info(stdout, card); tx_handle = i2s_example_init_pdm_tx(); ESP_ERROR_CHECK_WITHOUT_ABORT(play_wav("/sdcard/audio.wav")); // All done, unmount partition and disable SPI peripheral esp_vfs_fat_sdcard_unmount(mount_point, card); ESP_LOGI(TAG, "Card unmounted"); //deinitialize the bus after all devices are removed spi_bus_free(host.slot); }