├── cpp_example ├── data │ └── .keep ├── .gitignore ├── .vscode │ └── extensions.json ├── partitions.csv ├── test │ └── README ├── platformio.ini ├── lib │ └── README ├── include │ └── README └── src │ └── main.cpp ├── LICENSE ├── README.md └── python_example └── test.py /cpp_example/data/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cpp_example/.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /cpp_example/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /cpp_example/partitions.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | nvs, data, nvs, 0x9000, 0x5000, 3 | otadata, data, ota, 0xe000, 0x2000, 4 | app0, app, ota_0, 0x10000, 0x140000, 5 | app1, app, ota_1, 0x150000,0x140000, 6 | spiffs, data, spiffs, 0x290000,0x170000, -------------------------------------------------------------------------------- /cpp_example/test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /cpp_example/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:tinypico] 12 | platform = espressif32 13 | board = tinypico 14 | framework = arduino 15 | upload_port = /dev/cu.SLAB_USBtoUART 16 | monitor_port = /dev/cu.SLAB_USBtoUART 17 | monitor_speed = 115200 18 | board_build.partitions = partitions.csv 19 | -------------------------------------------------------------------------------- /cpp_example/lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/Z8Z734F5Y) 2 | 3 | # MicroPython and I2S Audio on the ESP32 4 | 5 | A quick demo of this [PR]() that adds support for I2S to MicroPython. 6 | 7 | You can watch a video of this in action [here](https://youtu.be/UXt27kOokh0) 8 | 9 | [![Demo Video](https://img.youtube.com/vi/UXt27kOokh0/0.jpg)](https://www.youtube.com/watch?v=UXt27kOokh0) 10 | 11 | I2S is not yet officially supported, but there is a Pull Request on GitHub that adds it in. 12 | 13 | We can build a custom version of Micropython pretty easily. We just need the ESP-IDF from here: https://github.com/espressif/esp-idf. 14 | 15 | And then we can get a copy of Micropython from here: https://github.com/micropython/micropython and merge in the pull request with I2S support: https://github.com/micropython/micropython/pull/7183 16 | 17 | The full commands to set up the IDF are: 18 | 19 | ``` 20 | git clone -b v4.2 --recursive https://github.com/espressif/esp-idf.git 21 | cd esp-idf 22 | ./install.sh 23 | . export.sh 24 | ``` 25 | 26 | And then to build our custom version of Micropython: 27 | 28 | ``` 29 | git clone git@github.com:micropython/micropython.git 30 | cd micropython 31 | git fetch origin pull/7183/head:i2s_support 32 | git merge i2s_support 33 | make -C mpy-cross 34 | cd ports/esp32 35 | make submodules 36 | make BOARD=UM_TINYPICO 37 | ``` 38 | 39 | You can find a bunch of sample code here for the I2S pull request: 40 | https://github.com/miketeachman/micropython-i2s-examples 41 | -------------------------------------------------------------------------------- /cpp_example/include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /python_example/test.py: -------------------------------------------------------------------------------- 1 | from machine import I2S 2 | from machine import Pin 3 | 4 | record_pin = Pin(23, Pin.IN, Pin.PULL_DOWN) 5 | 6 | 7 | def wait_for_button(): 8 | while record_pin.value() == 0: 9 | time.sleep_ms(100) 10 | time.sleep_ms(100) 11 | 12 | 13 | mic_sck_pin = Pin(26) 14 | mic_ws_pin = Pin(22) 15 | mic_sd_pin = Pin(21) 16 | 17 | audio_in = I2S( 18 | 0, 19 | sck=mic_sck_pin, 20 | ws=mic_ws_pin, 21 | sd=mic_sd_pin, 22 | mode=I2S.RX, 23 | bits=16, 24 | format=I2S.MONO, 25 | rate=16000, 26 | bufferlen=8192, 27 | ) 28 | 29 | spk_bck_pin = Pin(19) 30 | spk_ws_pin = Pin(27) 31 | spk_sdout_pin = Pin(18) 32 | 33 | audio_out = I2S( 34 | 1, 35 | sck=spk_bck_pin, 36 | ws=spk_ws_pin, 37 | sd=spk_sdout_pin, 38 | mode=I2S.TX, 39 | bits=16, 40 | format=I2S.MONO, 41 | rate=16000, 42 | bufferlen=8192, 43 | ) 44 | 45 | print("Press and hold button to record") 46 | 47 | wait_for_button() 48 | 49 | print("Recording") 50 | 51 | samples = bytearray(2048) 52 | 53 | with open("test.raw", "wb") as file: 54 | while record_pin.value() == 1: 55 | read_bytes = audio_in.readinto(samples) 56 | # amplify the signal to make it more audible 57 | I2S.shift(buf=samples, bits=16, shift=4) 58 | file.write(samples[:read_bytes]) 59 | 60 | print("Finished Recording") 61 | 62 | print("Processing data") 63 | 64 | print("Press the button to playback") 65 | 66 | wait_for_button() 67 | 68 | with open("test.raw", "rb") as file: 69 | samples_read = file.readinto(samples) 70 | while samples_read > 0: 71 | audio_out.write(samples[:samples_read]) 72 | samples_read = file.readinto(samples) 73 | 74 | print("Finished playback") 75 | -------------------------------------------------------------------------------- /cpp_example/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "driver/i2s.h" 4 | 5 | static const auto RECORD_PIN = GPIO_NUM_23; 6 | 7 | // i2s microphone pins 8 | static const i2s_pin_config_t i2s_mic_pins = { 9 | .bck_io_num = GPIO_NUM_26, 10 | .ws_io_num = GPIO_NUM_22, 11 | .data_out_num = I2S_PIN_NO_CHANGE, 12 | .data_in_num = GPIO_NUM_21}; 13 | 14 | // i2s config for reading from I2S 15 | static const i2s_config_t i2s_mic_Config = { 16 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX), 17 | .sample_rate = 16000, 18 | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, 19 | .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, 20 | .communication_format = I2S_COMM_FORMAT_I2S, 21 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, 22 | .dma_buf_count = 4, 23 | .dma_buf_len = 1024, 24 | .use_apll = false, 25 | .tx_desc_auto_clear = false, 26 | .fixed_mclk = 0}; 27 | 28 | // i2s speaker pins 29 | static const i2s_pin_config_t i2s_speaker_pins = { 30 | .bck_io_num = GPIO_NUM_19, 31 | .ws_io_num = GPIO_NUM_27, 32 | .data_out_num = GPIO_NUM_18, 33 | .data_in_num = I2S_PIN_NO_CHANGE}; 34 | 35 | // i2s config for speaker output - note this only outputs on the left channel 36 | static const i2s_config_t i2s_speaker_config = { 37 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), 38 | .sample_rate = 16000, 39 | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, 40 | .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, 41 | .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S), 42 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, 43 | .dma_buf_count = 4, 44 | .dma_buf_len = 1024, 45 | .use_apll = false, 46 | .tx_desc_auto_clear = true, 47 | .fixed_mclk = 0}; 48 | 49 | void setup() 50 | { 51 | Serial.begin(115200); 52 | // start up SPIFFS 53 | SPIFFS.begin(true); 54 | 55 | // set up the button 56 | pinMode(RECORD_PIN, INPUT_PULLDOWN); 57 | 58 | // start the i2s input 59 | 60 | //install and start i2s driver 61 | i2s_driver_install(I2S_NUM_0, &i2s_mic_Config, 0, NULL); 62 | // start the i2s input 63 | i2s_set_pin(I2S_NUM_0, &i2s_mic_pins); 64 | // clear the DMA buffers 65 | i2s_zero_dma_buffer(I2S_NUM_0); 66 | // and start 67 | i2s_start(I2S_NUM_0); 68 | 69 | // // start the i2s output 70 | 71 | //install and start i2s driver 72 | i2s_driver_install(I2S_NUM_1, &i2s_speaker_config, 0, NULL); 73 | // set up the i2s pins 74 | i2s_set_pin(I2S_NUM_1, &i2s_speaker_pins); 75 | // clear the DMA buffers 76 | i2s_zero_dma_buffer(I2S_NUM_1); 77 | // and start 78 | i2s_start(I2S_NUM_1); 79 | } 80 | 81 | void wait_for_button() 82 | { 83 | while (digitalRead(RECORD_PIN) == 0) 84 | { 85 | delay(100); 86 | } 87 | delay(100); 88 | } 89 | 90 | static int16_t samples[1024]; 91 | void loop() 92 | { 93 | Serial.println("Press and hold button to record"); 94 | 95 | wait_for_button(); 96 | 97 | Serial.println("Recording"); 98 | 99 | FILE *fp_write = fopen("/spiffs/test.raw", "wb"); 100 | while (digitalRead(RECORD_PIN)) 101 | { 102 | size_t bytes_read = 0; 103 | i2s_read(I2S_NUM_0, samples, 2048, &bytes_read, portMAX_DELAY); 104 | // amplify the signal 105 | for (int i = 0; i < bytes_read / 2; i++) 106 | { 107 | samples[i] = samples[i] << 4; 108 | } 109 | // save to file 110 | fwrite(samples, 1, bytes_read, fp_write); 111 | } 112 | fclose(fp_write); 113 | 114 | Serial.println("Finished Recording"); 115 | 116 | Serial.println("Press the button to playback"); 117 | 118 | wait_for_button(); 119 | 120 | FILE *fp_read = fopen("/spiffs/test.raw", "rb"); 121 | size_t read_bytes = fread(samples, 1, 2048, fp_read); 122 | while (read_bytes > 0) 123 | { 124 | size_t bytes_written = 0; 125 | i2s_write(I2S_NUM_1, samples, read_bytes, &bytes_written, portMAX_DELAY); 126 | read_bytes = fread(samples, 1, 2048, fp_read); 127 | } 128 | fclose(fp_read); 129 | 130 | Serial.println("Finished playback"); 131 | } --------------------------------------------------------------------------------