├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── examples ├── easy_wav_player.py ├── play_tone.py ├── play_wav_from_flash_blocking.py ├── play_wav_from_sdcard_blocking.py ├── play_wav_from_sdcard_non_blocking.py ├── play_wav_from_sdcard_uasyncio.py ├── record_mic_to_sdcard_blocking.py ├── record_mic_to_sdcard_non_blocking.py ├── record_mic_to_sdcard_uasyncio.py └── wavplayer.py ├── images ├── bucket.jpg ├── esp32_mic.jpg ├── esp32_uda.jpg ├── header_pins.jpg ├── jumper.jpg ├── pybd_mic.jpg ├── pybd_uda.jpg ├── pybv1_mic.jpg ├── pybv1_uda_22awg.jpg ├── pybv1_uda_jumpers.jpg └── solid_wire_22awg.jpg ├── teensy_audio_shield ├── play_teensy_audio_shield.py ├── play_teensy_audio_shield_esp32.py └── record_teensy_audio_shield.py └── wav ├── le-blues-de-la-vache-44k1-16bits-stereo.wav ├── music-16k-16bits-mono.wav ├── music-16k-16bits-stereo.wav ├── music-16k-32bits-mono.wav ├── music-16k-32bits-stereo.wav └── side-to-side-8k-16bits-stereo.wav /.gitattributes: -------------------------------------------------------------------------------- 1 | # Per default everything gets normalized and gets LF line endings on checkout. 2 | * text eol=lf 3 | 4 | # These will always have CRLF line endings on checkout. 5 | *.vcxproj text eol=crlf 6 | *.props text eol=crlf 7 | *.bat text eol=crlf 8 | 9 | # These are binary so should never be modified by git. 10 | *.png binary 11 | *.jpg binary 12 | *.dxf binary 13 | *.wav binary 14 | *.bin binary 15 | *.dfu binary 16 | *.uf2 binary 17 | *.hex binary -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .pydevproject -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Mike Teachman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MicroPython I2S Examples 2 | 3 | This repository provides MicroPython example code, showing how to use the I2S protocol with development boards supporting MicroPython. The I2S protocol can be used to play WAV audio files through a speaker or headphone, or to record microphone audio to a WAV file on a SD card. 4 | 5 | The examples are supported on 4 ports: stm32, esp32, rp2, and mimxrt. 6 | 7 | To use I2S with MicroPython on the Pyboards, ESP32, Raspberry Pi Pico, and mimxrt boards you will need to install a version of MicroPython firmware that supports I2S. For these ports, I2S is supported in the v1.20 release and all nightly builds. 8 | 9 | #### Development Boards Tested 10 | * stm32 port: 11 | * Pyboard D SF2W 12 | * Pyboard V1.1 13 | * esp32 port: 14 | * Adafruit Huzzah Feather ESP32 with external SD card 15 | * Lolin D32 Pro 16 | * Lolin D32 with external SD card 17 | * TinyPico with external SD card 18 | * rp2 port: 19 | * Raspberry Pi Pico 20 | * mimxrt port: 21 | * Teensy 4.0/4.1 22 | * MIMXRT evaluation boards 23 | 24 | #### I2S Microphone Boards Tested 25 | * INMP441 microphone module available on ebay, aliexpress, amazon 26 | * MSM261S4030H0 microphone module available on ebay, aliexpress, amazon 27 | * Adafruit I2S MEMS Microphone Breakout - SPH0645LM4H. See Workaround note below. 28 | 29 | #### I2S DAC and Amplifier Boards Tested 30 | * Adafruit I2S 3W Class D Amplifier Breakout - MAX98357A 31 | * Adafruit I2S Stereo Decoder - UDA1334A Breakout 32 | * I2S PCM5102 Stereo DAC Decoder available on ebay, aliexpress, amazon 33 | * Wondom 2 x 30W Class D Audio Amplifier Board & DAC, based on TI TAS5756 device 34 | * Custom board, based on TI TAS2505 Digital Input Class-D Speaker Amplifier 35 | * Teensy audio sheild for Teensy 4.x 36 | 37 | #### Quick Start - play an audio tone through ear phones 38 | The easiest way to get started with I2S is playing a pure tone to ear phones using a DAC board such as the I2S UDA1334A breakout board or the I2S PCM5102 Stereo DAC Decoder board. Here are the steps: 39 | 40 | 1. Download and program the appropriate firmware that supports the I2S protocol into the MicroPython development board 41 | 1. Load the example code `play_tone.py` into a text editor, found in the [examples](examples) folder 42 | 1. Make the following wiring connections using a quality breadboard and jumper wires. Use the GPIO pins that are listed in the example code file. Refer to the section on `Hardware Wiring Recommendations` below. 43 | 44 | |UDA1334A board pin|Pyboard V1.1 pin|Pyboard D pin|ESP32 pin|Pico Pin|Teensy 4.1 pin| 45 | |--|--|--|--|--|--| 46 | |3V0|3V3|3V3|3V3|3V3|3.3V| 47 | |GND|GND|GND|GND|GND|GND| 48 | |BCLK|Y6|Y6|32|16|4| 49 | |WSEL|Y5|Y5|25|17|3| 50 | |DIN|Y8|Y8|33|18|2| 51 | 52 | |PCM5102 board pin|Pyboard V1.1 pin|Pyboard D pin|ESP32 pin|Pico Pin|Teensy 4.1 pin| 53 | |--|--|--|--|--|--| 54 | |VIN|3V3|3V3|3V3|3V3|3.3V| 55 | |GND|GND|GND|GND|GND|GND| 56 | |SCK|GND|GND|GND|GND|GND| 57 | |BCK|Y6|Y6|32|16|4| 58 | |LCK|Y5|Y5|25|17|3| 59 | |DIN|Y8|Y8|33|18|2| 60 | 61 | 1. Establish a REPL connection to the board 62 | 1. Copy the code from the editor e.g. ctrl-A, ctrl-C 63 | 1. Ctrl-E in the REPL 64 | 1. Paste code into the REPL 65 | 1. Ctrl-D in the REPL to run the code 66 | 1. Result: the tone should play in the ear phones 67 | 1. Try different tone frequencies 68 | 69 | ### MicroPython examples 70 | MicroPython example code is contained in the [examples](examples) folder. WAV files used in the examples are contained in the [wav](wav) folder. 71 | 72 | Each example file has configuration parameters, marked with 73 | 74 | `# ======= AUDIO CONFIGURATION =======` 75 | 76 | and 77 | 78 | `# ======= I2S CONFIGURATION =======` 79 | 80 | Each example supports all MicroPython ports that offer I2S. This has the benefit of having fewer example files, but comes at the cost of large example files (e.g. many blocks of code that start with lines like `elif os.uname().machine.count("Raspberry"):`). Examples can be simplified by removing the blocks of port-specific code that are not needed for a particular development board. 81 | 82 | #### PyBoard GPIO Pins 83 | 84 | All Pyboard V1.1 and Pyboard D examples use the following I2S peripheral ID and GPIO pins 85 | 86 | |I2S ID|SCK pin|WS pin|SD pin| 87 | |--|--|--|--| 88 | |2|Y6|Y5|Y8| 89 | 90 | To use different GPIO mappings refer to the sections below 91 | 92 | #### ESP32 GPIO Pins 93 | 94 | All ESP32 examples use the following I2S peripheral ID and GPIO pins 95 | 96 | |I2S ID|SCK pin|WS pin|SD pin| 97 | |--|--|--|--| 98 | |0|32|25|33| 99 | 100 | To use different GPIO mappings refer to the sections below 101 | 102 | #### Raspberry Pi Pico GPIO Pins 103 | 104 | All Pico examples use the following I2S peripheral ID and GPIO pins 105 | 106 | |I2S ID|SCK pin|WS pin|SD pin| 107 | |--|--|--|--| 108 | |0|16|17|18| 109 | 110 | To use different GPIO mappings refer to the sections below 111 | 112 | #### MIMXRT (Teensy 4.0/4.1) GPIO Pins 113 | 114 | All MIMXRT examples are designed for the Teensy 4.0/4.1 boards and use the following I2S peripheral ID and GPIO pins. **Note: the Teensy 4.1 is a better choice compared to the Teensy 4.0**. The Teensy 4.1 has a built-in SD card slot that can be used with the `machine.SDCard` class, which offers fast SD card access. The Teensy 4.0 requires an external SD card module, which only works with the slower `sdcard.py` driver. 115 | 116 | For transmitting to a DAC: 117 | |I2S ID|SCK pin|WS pin|SD pin| 118 | |--|--|--|--| 119 | |2|4|3|2| 120 | 121 | For receiving from a microphone: 122 | |I2S ID|SCK pin|WS pin|SD pin| 123 | |--|--|--|--| 124 | |1|21|20|8| 125 | 126 | 127 | To use different GPIO mappings refer to the sections below 128 | 129 | #### Easy WAV Player example 130 | The file `easy_wav_player.py` contains an easy-to-use micropython example for playing WAV files. This example requires 131 | an SD card (to store the WAV files). Pyboards have a built in SD card. Some ESP32 development boards have a built-in SD Card, such as the Lolin D32 Pro. Other devices, such as the TinyPico and Raspberry Pi Pico require an external SD card module to be wired in. Additionally, for the Raspberry Pi Pico [sdcard.py](https://github.com/micropython/micropython-lib/blob/master/micropython/drivers/storage/sdcard/sdcard.py) needs to be copied to the Pico's filesystem to enable SD card support. 132 | 133 | Instructions 134 | 1. Wire up the hardware. e.g. connect the I2S playback module to the development board, and connect an external SD Card Module (if needed). See tips on hardware wiring below. The example uses the default GPIO pins outlined above. These can 135 | be customized, if needed. 136 | 1. copy file `wavplayer.py` to the internal flash file system using a command line tool such as ampy or rshell. The Thonny IDE also offers an easy way to copy this file (View->Files, `Upload to /` option). 137 | 1. copy the WAV file(s) you want to play to an SD card. Plug the SD card into the SD card Module. 138 | 1. configure the file `easy_wav_player.py` to specify the WAV file(s) to play 139 | 1. copy the file `easy_wav_player.py` to the internal flash file system using a command line tool such as ampy or rshell. The Thonny IDE also offers an easy way to copy this file (View->Files, `Upload to /` option). 140 | 1. run `easy_wav_player.py` by importing the file into the REPL. e.g. import easy_wav_player 141 | 1. try various ways of playing a WAV file, using the `pause()`, `resume()`, and `stop()` methods 142 | 143 | MP3 files can be converted to WAV files using online applications such as 144 | [online-convert](https://audio.online-convert.com/convert-to-wav) 145 | 146 | WAV file tag data can be inspected using a downloadable application such as 147 | [MediaInfo](https://mediaarea.net/en/MediaInfo). 148 | This application is useful to check the sample rate, stereo versus mono, and sample bit size (16, 24, or 32 bits) 149 | 150 | #### Pyboard GPIO mappings for SCK, WS, SD 151 | 152 | On Pyboard devices I2S compatible GPIO pins are mapped to a specific I2S hardware bus. The tables below show this mapping. For example, the GPIO pin "Y6" can only be used with I2S ID=2. 153 | 154 | Pyboard D with MicroPython WBUS-DIP28 adapter 155 | 156 | |I2S ID|SCK pin|WS pin|SD pin| 157 | |--|--|--|--| 158 | |1|X6,W29|X5,W16|Y4| 159 | |2|Y1,Y6,Y9|Y3,Y5|Y8,W24| 160 | 161 | Pyboard V1.0/V1.1 162 | 163 | |I2S ID|SCK pin|WS pin|SD pin| 164 | |--|--|--|--| 165 | |2|Y6,Y9|Y4,Y5|Y8,X22| 166 | 167 | #### ESP32 GPIO mappings for SCK, WS, SD 168 | 169 | All ESP32 GPIO pins can be used for I2S, with attention to special cases: 170 | * GPIO34 to GPIO39 are input-only 171 | * GPIO strapping pins: see note below on using strapping pins 172 | 173 | Strapping Pin consideration: 174 | The following ESP32 GPIO strapping pins should be **used with caution**. There is a risk that the state of the attached hardware can affect the boot sequence. When possible, use other GPIO pins. 175 | * GPIO0 - used to detect boot-mode. Bootloader runs when pin is low during powerup. Internal pull-up resistor. 176 | * GPIO2 - used to enter serial bootloader. Internal pull-down resistor. 177 | * GPIO4 - technical reference indicates this is a strapping pin, but usage is not described. Internal pull-down resistor. 178 | * GPIO5 - used to configure SDIO Slave. Internal pull-up resistor. 179 | * GPIO12 - used to select flash voltage. Internal pull-down resistor. 180 | * GPIO15 - used to configure silencing of boot messages. Internal pull-up resistor. 181 | 182 | #### Raspberry Pi Pico GPIO mappings for SCK, WS, SD 183 | 184 | All Pico GPIO pins can be used for I2S, with one limitation. The WS pin number must be one greater than the SCK pin number. 185 | 186 | #### MIMXRT GPIO mappings for SCK, WS, SD, MCK 187 | 188 | On boards supporting NXP i.MX RT processors I2S compatible GPIO pins are mapped to a specific I2S hardware bus. In addition, GPIO pins are further specified as either Transmit or Receive. The tables below show this mapping for 3 boards. For example, the GPIO pin 4 can be used with I2S ID=2 and transmitting to a DAC. Other I2S pin mapping combinations exist, but are not needed for simple-to-use I2S hardware, such as the INMP441 microphone, or the I2S PCM5102 Stereo DAC Decoder. 189 | 190 | Teensy 4.1 191 | 192 | For transmitting to a DAC: 193 | |I2S ID|SCK pin|WS pin|SD pin|MCK pin| 194 | |--|--|--|--|--| 195 | |1|26,36|27,37|39,7|23| 196 | |2|4|3|2|33| 197 | 198 | For receiving from a microphone: 199 | |I2S ID|SCK pin|WS pin|SD pin|MCK pin| 200 | |--|--|--|--|--| 201 | |1|21|20|38,8|23| 202 | 203 | Teensy 4.0 204 | 205 | For transmitting to a DAC: 206 | |I2S ID|SCK pin|WS pin|SD pin|MCK pin| 207 | |--|--|--|--|--| 208 | |1|26,36|27,37|7|23| 209 | |2|4|3|2|33| 210 | 211 | For receiving from a microphone: 212 | |I2S ID|SCK pin|WS pin|SD pin|MCK pin| 213 | |--|--|--|--|--| 214 | |1|21|20|8|23| 215 | 216 | Seeed Arch Mix 217 | 218 | For transmitting to a DAC: 219 | |I2S ID|SCK pin|WS pin|SD pin|MCK pin| 220 | |--|--|--|--|--| 221 | |1|J4 14|J4 15|J4 13|J4 09| 222 | 223 | For receiving from a microphone: 224 | |I2S ID|SCK pin|WS pin|SD pin|MCK pin| 225 | |--|--|--|--|--| 226 | |1|J4 11|J4 10|J4 12|J4 09| 227 | 228 | ### Generating a Master Clock using machine.PWM 229 | 230 | Some audio codecs require a Master Clock signal at a typical frequency of 256 * sampling frequency. 231 | Only the mimxrt port supports Master Clock generation in the I2S class. For other ports, the machine.PWM 232 | class offers a convenient way to generate the Master Clock signal. Here is some [example code](teensy_audio_shield/play_teensy_audio_shield_esp32.py) showing how to generate a Master Clock for the teensy audio shield. 233 | 234 | ### Hardware Wiring Recommendations 235 | 236 | I have found the best audio quality is acheived when: 237 | 238 | 1. wires are short 239 | 1. modules are connected with header pins and 10cm long female-female jumpers, OR 240 | 1. solid core 22 AWG wire 241 | 242 | ![headers](images/header_pins.jpg) 243 | 244 | ![jumper](images/jumper.jpg) 245 | 246 | ![wire_22_awg](images/solid_wire_22awg.jpg) 247 | 248 | The following images show example connections between microcontroller boards and breakout boards. The following colour conventions are used for the signals: 249 | 250 | |Signal|Colour| 251 | |--|--| 252 | |+3.3V|Red| 253 | |GND|Black| 254 | |SCK|White| 255 | |WS|Blue| 256 | |SD|Yellow| 257 | 258 | #### UDA1334A DAC board with Pyboard V1.1 259 | 260 | Connections made with Female-Female jumpers and header pins 261 | 262 | ![pybv11_uda_jump](images/pybv1_uda_jumpers.jpg) 263 | 264 | Connections made with 22 AWG wire 265 | 266 | ![pybv11_uda_wire](images/pybv1_uda_22awg.jpg) 267 | 268 | #### UDA1334A DAC board with Pyboard D 269 | 270 | Connections made with Female-Female jumpers and header pins 271 | 272 | ![pybd_uda](images/pybd_uda.jpg) 273 | 274 | #### UDA1334A DAC board with ESP32 275 | 276 | Connections made with Female-Female jumpers and header pins 277 | 278 | ![esp32_uda](images/esp32_uda.jpg) 279 | 280 | #### INMP441 microphone board with Pyboard V1.1 281 | 282 | Connections made with Female-Female jumpers and header pins 283 | 284 | ![pybv11_mic](images/pybv1_mic.jpg) 285 | 286 | #### INMP441 microphone board with Pyboard D 287 | 288 | Connections made with Female-Female jumpers and header pins 289 | 290 | ![pybd_mic](images/pybd_mic.jpg) 291 | 292 | #### INMP441 microphone board with ESP32 293 | 294 | Connections made with Female-Female jumpers and header pins 295 | 296 | ![esp32_mic](images/esp32_mic.jpg) 297 | 298 | ### Projects that use I2S 299 | 1. [Micro-gui audio demo](https://github.com/peterhinch/micropython-micro-gui/blob/main/gui/demos/audio.py) 300 | 2. [Street Sense](https://hackaday.io/project/162059-street-sense) 301 | 3. [Tensorflow Micropython Examples - Micro-speech](https://github.com/mocleiri/tensorflow-micropython-examples/tree/main/examples/micro-speech) 302 | 303 | ### Explaining the I2S protocol with buckets and water 304 | 305 | The I2S protocol is different than other protocols such as I2C and SPI. Those protocols are transactional. A controller requests data from a peripheral and waits for a reply. I2S is a streaming protocol. Data flows continuously, ideally without gaps. 306 | 307 | It's interesting to use a water and bucket analogy for the MicroPython I2S implementation. Consider writing a DAC using I2S. The internal buffer(ibuf) can be considered as a large bucket of water, with a hole in the bottom that drains the bucket. The water streaming out of the bottom is analogous to the flow of audio samples going into the I2S hardware. That flow must be constant and at a fixed rate. The user facing buffer is like a small bucket that is used to fill the large bucket. In the case of I2S writes, the small bucket is used to transport audio samples from a Wav file "lake" and fill the large bucket (ibuf). Imagine a person using the small bucket to move audio samples from the Wav file to the large bucket; if the large bucket becomes full, the person might go do another task, and come back later to see if there is more room in the large bucket. When they return, if there is space in the large bucket, they will pour some more water (samples) into the large bucket. Initially, the large buffer is empty. Almost immediately after water is poured into the large bucket audio samples stream out of the bottom and sound is heard almost immediately. After the last small bucket is poured into the large bucket it will take some time to drain the large bucket -- sound will be heard for some amount of time after the last small bucket is poured in. 308 | 309 | If the person is too slow to refill the large bucket it will run dry and the water flow stops, a condition called "underflow" -- there will be a gap in sound produced. 310 | 311 | Does a water analogy help to explain I2S? comments welcome ! 312 | 313 | ![bucket_analogy](images/bucket.jpg) 314 | 315 | ### FAQ 316 | Q: Are there sizing guidelines for the internal buffer (ibuf)? 317 | A: For playback of wave files, a good starting point is to size the ibuf = 2x user buffer size. For example, if the user buffer is 10kB, ibuf could be sized at 20kB. If gaps are detected in the audio playback increasing the size of ibuf may mitigate the gaps. For microphone recordings, ibuf will often need to be much larger. Unlike reads, data writes to most SD cards are not consistent - some writes can take much longer. For example, the average write speed might be 1000kB/s, but some writes may slow to 10kB/s. Having a large ibuf accommodates these periods of slow data writes. Consider starting with ibuf = 4x user buffer size. Increase the ibuf size if gaps are detected in the recording. 318 | 319 | Q: How many seconds of audio data is held in the internal buffer (ibuf)? 320 | A: T[seconds] = ibuf-size-in-bytes / sample-rate-in-samples-per-second / num-channels / sample-size-in-bytes 321 | stereo = 2 channels, mono = 1 channel. 322 | 323 | Q: Are there sizing guidelines for the user buffer? 324 | A: Smaller sizes will favour efficient use of heap space, but suffer from the inherent inefficiency of more switching between filling and emptying. A larger user buffer size suffers from a longer time of processing the samples or time to fill from a SD card - this longer time may block critical time functions from running. A good starting point is a user buffer of 5kB. 325 | 326 | Q: What conditions causes gaps in the sample stream? 327 | A: For writes to a DAC, a gap will happen when the internal buffer is filled at a slower rate than samples being sent to the I2S DAC. This is called underflow. For reads from a microphone, a gap will happen when the internal buffer is emptied at a slower rate than sample data is being read from the microphone. This is called overflow. 328 | 329 | Q: Does the MicroPython I2S class support devices that need a MCK signal? 330 | A: Yes, one port currently supports MCK. The mimxrt port of I2S allows a MCK output to be defined. Most devices that are popular with users do not need a MCK signal. 331 | 332 | #### Workaround for Adafruit I2S MEMS Microphone Breakout - SPH0645LM4H 333 | This is a well designed breakout board based on the SPH0645LM4H microphone device. Users need to be aware that the SPH0645LM4H device implements non-standard Philips I2S timing. When used with the ESP32, all audio samples coming from the I2S microphone are shifted to the left by one bit. This increases the sound level by 6dB. More details on this problem are outlined a [StreetSense project log](https://hackaday.io/project/162059-street-sense/log/160705-new-i2s-microphone). 334 | Workaround: Use the static I2S class method `shift()` to right shift all samples that are read from the microphone. 335 | 336 | #### Attributions 337 | "Le blues de la vache" by Le Collectif Unifié de la Crécelle is licensed under CC BY-NC-SA 2.1 338 | -------------------------------------------------------------------------------- /examples/easy_wav_player.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # Copyright (c) 2022 Mike Teachman 3 | # https://opensource.org/licenses/MIT 4 | # 5 | # Purpose: Play a WAV audio file out of a speaker or headphones 6 | # 7 | 8 | import os 9 | import time 10 | from machine import Pin 11 | 12 | from wavplayer import WavPlayer 13 | 14 | if os.uname().machine.count("PYBv1"): 15 | 16 | # ======= I2S CONFIGURATION ======= 17 | SCK_PIN = "Y6" 18 | WS_PIN = "Y5" 19 | SD_PIN = "Y8" 20 | I2S_ID = 2 21 | BUFFER_LENGTH_IN_BYTES = 40000 22 | # ======= I2S CONFIGURATION ======= 23 | 24 | elif os.uname().machine.count("PYBD"): 25 | import pyb 26 | 27 | pyb.Pin("EN_3V3").on() # provide 3.3V on 3V3 output pin 28 | 29 | # ======= SD CARD CONFIGURATION ======= 30 | os.mount(pyb.SDCard(), "/sd") 31 | # ======= SD CARD CONFIGURATION ======= 32 | 33 | # ======= I2S CONFIGURATION ======= 34 | SCK_PIN = "Y6" 35 | WS_PIN = "Y5" 36 | SD_PIN = "Y8" 37 | I2S_ID = 2 38 | BUFFER_LENGTH_IN_BYTES = 40000 39 | # ======= I2S CONFIGURATION ======= 40 | 41 | elif os.uname().machine.count("ESP32"): 42 | from machine import SDCard 43 | 44 | # ======= SD CARD CONFIGURATION ======= 45 | sd = SDCard(slot=2) # sck=18, mosi=23, miso=19, cs=5 46 | os.mount(sd, "/sd") 47 | # ======= SD CARD CONFIGURATION ======= 48 | 49 | # ======= I2S CONFIGURATION ======= 50 | SCK_PIN = 32 51 | WS_PIN = 25 52 | SD_PIN = 33 53 | I2S_ID = 0 54 | BUFFER_LENGTH_IN_BYTES = 40000 55 | # ======= I2S CONFIGURATION ======= 56 | 57 | elif os.uname().machine.count("Raspberry"): 58 | from sdcard import SDCard 59 | from machine import SPI 60 | 61 | cs = Pin(13, machine.Pin.OUT) 62 | spi = SPI( 63 | 1, 64 | baudrate=1_000_000, # this has no effect on spi bus speed to SD Card 65 | polarity=0, 66 | phase=0, 67 | bits=8, 68 | firstbit=machine.SPI.MSB, 69 | sck=Pin(14), 70 | mosi=Pin(15), 71 | miso=Pin(12), 72 | ) 73 | 74 | # ======= SD CARD CONFIGURATION ======= 75 | sd = SDCard(spi, cs) 76 | sd.init_spi(25_000_000) # increase SPI bus speed to SD card 77 | os.mount(sd, "/sd") 78 | # ======= SD CARD CONFIGURATION ======= 79 | 80 | # ======= I2S CONFIGURATION ======= 81 | SCK_PIN = 16 82 | WS_PIN = 17 83 | SD_PIN = 18 84 | I2S_ID = 0 85 | BUFFER_LENGTH_IN_BYTES = 40000 86 | # ======= I2S CONFIGURATION ======= 87 | 88 | elif os.uname().machine.count("MIMXRT"): 89 | from machine import SDCard 90 | 91 | # ======= SD CARD CONFIGURATION ======= 92 | sd = SDCard(1) # Teensy 4.1: sck=45, mosi=43, miso=42, cs=44 93 | os.mount(sd, "/sd") 94 | # ======= SD CARD CONFIGURATION ======= 95 | 96 | # ======= I2S CONFIGURATION ======= 97 | SCK_PIN = 4 98 | WS_PIN = 3 99 | SD_PIN = 2 100 | I2S_ID = 2 101 | BUFFER_LENGTH_IN_BYTES = 40000 102 | # ======= I2S CONFIGURATION ======= 103 | 104 | else: 105 | print("Warning: program not tested with this board") 106 | 107 | wp = WavPlayer( 108 | id=I2S_ID, 109 | sck_pin=Pin(SCK_PIN), 110 | ws_pin=Pin(WS_PIN), 111 | sd_pin=Pin(SD_PIN), 112 | ibuf=BUFFER_LENGTH_IN_BYTES, 113 | ) 114 | 115 | wp.play("music-16k-16bits-stereo.wav", loop=False) 116 | # wait until the entire WAV file has been played 117 | while wp.isplaying() == True: 118 | # other actions can be done inside this loop during playback 119 | pass 120 | 121 | wp.play("music-16k-16bits-mono.wav", loop=False) 122 | time.sleep(10) # play for 10 seconds 123 | wp.pause() 124 | time.sleep(5) # pause playback for 5 seconds 125 | wp.resume() # continue playing to the end of the WAV file 126 | -------------------------------------------------------------------------------- /examples/play_tone.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # Copyright (c) 2022 Mike Teachman 3 | # https://opensource.org/licenses/MIT 4 | 5 | # Purpose: Play a pure audio tone out of a speaker or headphones 6 | # 7 | # - write audio samples containing a pure tone to an I2S amplifier or DAC module 8 | # - tone will play continuously in a loop until 9 | # a keyboard interrupt is detected or the board is reset 10 | # 11 | # Blocking version 12 | # - the write() method blocks until the entire sample buffer is written to I2S 13 | 14 | import os 15 | import math 16 | import struct 17 | from machine import I2S 18 | from machine import Pin 19 | 20 | def make_tone(rate, bits, frequency): 21 | # create a buffer containing the pure tone samples 22 | samples_per_cycle = rate // frequency 23 | sample_size_in_bytes = bits // 8 24 | samples = bytearray(samples_per_cycle * sample_size_in_bytes) 25 | volume_reduction_factor = 32 26 | range = pow(2, bits) // 2 // volume_reduction_factor 27 | 28 | if bits == 16: 29 | format = "Files, `Upload to /` option). 17 | 18 | 19 | import os 20 | from machine import I2S 21 | from machine import Pin 22 | 23 | if os.uname().machine.count("PYBv1"): 24 | 25 | # ======= I2S CONFIGURATION ======= 26 | SCK_PIN = "Y6" 27 | WS_PIN = "Y5" 28 | SD_PIN = "Y8" 29 | I2S_ID = 2 30 | BUFFER_LENGTH_IN_BYTES = 5000 31 | # ======= I2S CONFIGURATION ======= 32 | 33 | elif os.uname().machine.count("PYBD"): 34 | import pyb 35 | 36 | pyb.Pin("EN_3V3").on() # provide 3.3V on 3V3 output pin 37 | 38 | # ======= I2S CONFIGURATION ======= 39 | SCK_PIN = "Y6" 40 | WS_PIN = "Y5" 41 | SD_PIN = "Y8" 42 | I2S_ID = 2 43 | BUFFER_LENGTH_IN_BYTES = 5000 44 | # ======= I2S CONFIGURATION ======= 45 | 46 | elif os.uname().machine.count("ESP32"): 47 | 48 | # ======= I2S CONFIGURATION ======= 49 | SCK_PIN = 32 50 | WS_PIN = 25 51 | SD_PIN = 33 52 | I2S_ID = 0 53 | BUFFER_LENGTH_IN_BYTES = 5000 54 | # ======= I2S CONFIGURATION ======= 55 | 56 | elif os.uname().machine.count("Raspberry"): 57 | 58 | # ======= I2S CONFIGURATION ======= 59 | SCK_PIN = 16 60 | WS_PIN = 17 61 | SD_PIN = 18 62 | I2S_ID = 0 63 | BUFFER_LENGTH_IN_BYTES = 5000 64 | # ======= I2S CONFIGURATION ======= 65 | 66 | elif os.uname().machine.count("MIMXRT"): 67 | 68 | # ======= I2S CONFIGURATION ======= 69 | SCK_PIN = 4 70 | WS_PIN = 3 71 | SD_PIN = 2 72 | I2S_ID = 2 73 | BUFFER_LENGTH_IN_BYTES = 5000 74 | # ======= I2S CONFIGURATION ======= 75 | 76 | else: 77 | print("Warning: program not tested with this board") 78 | 79 | # ======= AUDIO CONFIGURATION ======= 80 | WAV_FILE = "side-to-side-8k-16bits-stereo.wav" 81 | WAV_SAMPLE_SIZE_IN_BITS = 16 82 | FORMAT = I2S.STEREO 83 | SAMPLE_RATE_IN_HZ = 8000 84 | # ======= AUDIO CONFIGURATION ======= 85 | 86 | audio_out = I2S( 87 | I2S_ID, 88 | sck=Pin(SCK_PIN), 89 | ws=Pin(WS_PIN), 90 | sd=Pin(SD_PIN), 91 | mode=I2S.TX, 92 | bits=WAV_SAMPLE_SIZE_IN_BITS, 93 | format=FORMAT, 94 | rate=SAMPLE_RATE_IN_HZ, 95 | ibuf=BUFFER_LENGTH_IN_BYTES, 96 | ) 97 | 98 | wav = open(WAV_FILE, "rb") 99 | pos = wav.seek(44) # advance to first byte of Data section in WAV file 100 | 101 | # allocate sample array 102 | # memoryview used to reduce heap allocation 103 | wav_samples = bytearray(1000) 104 | wav_samples_mv = memoryview(wav_samples) 105 | 106 | # continuously read audio samples from the WAV file 107 | # and write them to an I2S DAC 108 | print("========== START PLAYBACK ==========") 109 | try: 110 | while True: 111 | num_read = wav.readinto(wav_samples_mv) 112 | # end of WAV file? 113 | if num_read == 0: 114 | # end-of-file, advance to first byte of Data section 115 | _ = wav.seek(44) 116 | else: 117 | _ = audio_out.write(wav_samples_mv[:num_read]) 118 | 119 | except (KeyboardInterrupt, Exception) as e: 120 | print("caught exception {} {}".format(type(e).__name__, e)) 121 | 122 | # cleanup 123 | wav.close() 124 | audio_out.deinit() 125 | print("Done") 126 | -------------------------------------------------------------------------------- /examples/play_wav_from_sdcard_blocking.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # Copyright (c) 2022 Mike Teachman 3 | # https://opensource.org/licenses/MIT 4 | 5 | # Purpose: Play a WAV audio file out of a speaker or headphones 6 | # 7 | # - read audio samples from a WAV file on SD Card 8 | # - write audio samples to an I2S amplifier or DAC module 9 | # - the WAV file will play continuously in a loop until 10 | # a keyboard interrupt is detected or the board is reset 11 | # 12 | # blocking version 13 | # - the write() method blocks until the entire sample buffer is written to the I2S interface 14 | 15 | import os 16 | from machine import I2S 17 | from machine import Pin 18 | 19 | if os.uname().machine.count("PYBv1"): 20 | 21 | # ======= I2S CONFIGURATION ======= 22 | SCK_PIN = "Y6" 23 | WS_PIN = "Y5" 24 | SD_PIN = "Y8" 25 | I2S_ID = 2 26 | BUFFER_LENGTH_IN_BYTES = 40000 27 | # ======= I2S CONFIGURATION ======= 28 | 29 | elif os.uname().machine.count("PYBD"): 30 | import pyb 31 | 32 | pyb.Pin("EN_3V3").on() # provide 3.3V on 3V3 output pin 33 | os.mount(pyb.SDCard(), "/sd") 34 | 35 | # ======= I2S CONFIGURATION ======= 36 | SCK_PIN = "Y6" 37 | WS_PIN = "Y5" 38 | SD_PIN = "Y8" 39 | I2S_ID = 2 40 | BUFFER_LENGTH_IN_BYTES = 40000 41 | # ======= I2S CONFIGURATION ======= 42 | 43 | elif os.uname().machine.count("ESP32"): 44 | from machine import SDCard 45 | 46 | sd = SDCard(slot=2) # sck=18, mosi=23, miso=19, cs=5 47 | os.mount(sd, "/sd") 48 | 49 | # ======= I2S CONFIGURATION ======= 50 | SCK_PIN = 32 51 | WS_PIN = 25 52 | SD_PIN = 33 53 | I2S_ID = 0 54 | BUFFER_LENGTH_IN_BYTES = 40000 55 | # ======= I2S CONFIGURATION ======= 56 | 57 | elif os.uname().machine.count("Raspberry"): 58 | from sdcard import SDCard 59 | from machine import SPI 60 | 61 | cs = Pin(13, machine.Pin.OUT) 62 | spi = SPI( 63 | 1, 64 | baudrate=1_000_000, # this has no effect on spi bus speed to SD Card 65 | polarity=0, 66 | phase=0, 67 | bits=8, 68 | firstbit=machine.SPI.MSB, 69 | sck=Pin(14), 70 | mosi=Pin(15), 71 | miso=Pin(12), 72 | ) 73 | 74 | sd = SDCard(spi, cs) 75 | sd.init_spi(25_000_000) # increase SPI bus speed to SD card 76 | os.mount(sd, "/sd") 77 | 78 | # ======= I2S CONFIGURATION ======= 79 | SCK_PIN = 16 80 | WS_PIN = 17 81 | SD_PIN = 18 82 | I2S_ID = 0 83 | BUFFER_LENGTH_IN_BYTES = 40000 84 | # ======= I2S CONFIGURATION ======= 85 | 86 | elif os.uname().machine.count("MIMXRT"): 87 | from machine import SDCard 88 | 89 | sd = SDCard(1) # Teensy 4.1: sck=45, mosi=43, miso=42, cs=44 90 | os.mount(sd, "/sd") 91 | 92 | # ======= I2S CONFIGURATION ======= 93 | SCK_PIN = 4 94 | WS_PIN = 3 95 | SD_PIN = 2 96 | I2S_ID = 2 97 | BUFFER_LENGTH_IN_BYTES = 40000 98 | # ======= I2S CONFIGURATION ======= 99 | 100 | else: 101 | print("Warning: program not tested with this board") 102 | 103 | # ======= AUDIO CONFIGURATION ======= 104 | WAV_FILE = "music-16k-32bits-stereo.wav" 105 | WAV_SAMPLE_SIZE_IN_BITS = 32 106 | FORMAT = I2S.STEREO 107 | SAMPLE_RATE_IN_HZ = 16000 108 | # ======= AUDIO CONFIGURATION ======= 109 | 110 | audio_out = I2S( 111 | I2S_ID, 112 | sck=Pin(SCK_PIN), 113 | ws=Pin(WS_PIN), 114 | sd=Pin(SD_PIN), 115 | mode=I2S.TX, 116 | bits=WAV_SAMPLE_SIZE_IN_BITS, 117 | format=FORMAT, 118 | rate=SAMPLE_RATE_IN_HZ, 119 | ibuf=BUFFER_LENGTH_IN_BYTES, 120 | ) 121 | 122 | wav = open("/sd/{}".format(WAV_FILE), "rb") 123 | _ = wav.seek(44) # advance to first byte of Data section in WAV file 124 | 125 | # allocate sample array 126 | # memoryview used to reduce heap allocation 127 | wav_samples = bytearray(10000) 128 | wav_samples_mv = memoryview(wav_samples) 129 | 130 | # continuously read audio samples from the WAV file 131 | # and write them to an I2S DAC 132 | print("========== START PLAYBACK ==========") 133 | try: 134 | while True: 135 | num_read = wav.readinto(wav_samples_mv) 136 | # end of WAV file? 137 | if num_read == 0: 138 | # end-of-file, advance to first byte of Data section 139 | _ = wav.seek(44) 140 | else: 141 | _ = audio_out.write(wav_samples_mv[:num_read]) 142 | except (KeyboardInterrupt, Exception) as e: 143 | print("caught exception {} {}".format(type(e).__name__, e)) 144 | 145 | # cleanup 146 | wav.close() 147 | if os.uname().machine.count("PYBD"): 148 | os.umount("/sd") 149 | elif os.uname().machine.count("ESP32"): 150 | os.umount("/sd") 151 | sd.deinit() 152 | elif os.uname().machine.count("Raspberry"): 153 | os.umount("/sd") 154 | spi.deinit() 155 | elif os.uname().machine.count("MIMXRT"): 156 | os.umount("/sd") 157 | sd.deinit() 158 | audio_out.deinit() 159 | print("Done") 160 | -------------------------------------------------------------------------------- /examples/play_wav_from_sdcard_non_blocking.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # Copyright (c) 2022 Mike Teachman 3 | # https://opensource.org/licenses/MIT 4 | 5 | # Purpose: Play a WAV audio file out of a speaker or headphones 6 | # 7 | # - read audio samples from a WAV file on SD Card 8 | # - write audio samples to an I2S amplifier or DAC module 9 | # - the WAV file will play continuously in a loop until 10 | # a keyboard interrupt is detected or the board is reset 11 | # 12 | # non-blocking version 13 | # - the write() method is non-blocking. 14 | # - a callback function is called when all sample data has been written to the I2S interface 15 | # - a callback() method sets the callback function 16 | 17 | import os 18 | import time 19 | import micropython 20 | from machine import I2S 21 | from machine import Pin 22 | 23 | if os.uname().machine.count("PYBv1"): 24 | 25 | # ======= I2S CONFIGURATION ======= 26 | SCK_PIN = "Y6" 27 | WS_PIN = "Y5" 28 | SD_PIN = "Y8" 29 | I2S_ID = 2 30 | BUFFER_LENGTH_IN_BYTES = 40000 31 | # ======= I2S CONFIGURATION ======= 32 | 33 | elif os.uname().machine.count("PYBD"): 34 | import pyb 35 | 36 | pyb.Pin("EN_3V3").on() # provide 3.3V on 3V3 output pin 37 | os.mount(pyb.SDCard(), "/sd") 38 | 39 | # ======= I2S CONFIGURATION ======= 40 | SCK_PIN = "Y6" 41 | WS_PIN = "Y5" 42 | SD_PIN = "Y8" 43 | I2S_ID = 2 44 | BUFFER_LENGTH_IN_BYTES = 40000 45 | # ======= I2S CONFIGURATION ======= 46 | 47 | elif os.uname().machine.count("ESP32"): 48 | from machine import SDCard 49 | 50 | sd = SDCard(slot=2) # sck=18, mosi=23, miso=19, cs=5 51 | os.mount(sd, "/sd") 52 | 53 | # ======= I2S CONFIGURATION ======= 54 | SCK_PIN = 32 55 | WS_PIN = 25 56 | SD_PIN = 33 57 | I2S_ID = 0 58 | BUFFER_LENGTH_IN_BYTES = 40000 59 | # ======= I2S CONFIGURATION ======= 60 | 61 | elif os.uname().machine.count("Raspberry"): 62 | from sdcard import SDCard 63 | from machine import SPI 64 | 65 | cs = Pin(13, machine.Pin.OUT) 66 | spi = SPI( 67 | 1, 68 | baudrate=1_000_000, # this has no effect on spi bus speed to SD Card 69 | polarity=0, 70 | phase=0, 71 | bits=8, 72 | firstbit=machine.SPI.MSB, 73 | sck=Pin(14), 74 | mosi=Pin(15), 75 | miso=Pin(12), 76 | ) 77 | 78 | sd = SDCard(spi, cs) 79 | sd.init_spi(25_000_000) # increase SPI bus speed to SD card 80 | os.mount(sd, "/sd") 81 | 82 | # ======= I2S CONFIGURATION ======= 83 | SCK_PIN = 16 84 | WS_PIN = 17 85 | SD_PIN = 18 86 | I2S_ID = 0 87 | BUFFER_LENGTH_IN_BYTES = 40000 88 | # ======= I2S CONFIGURATION ======= 89 | 90 | elif os.uname().machine.count("MIMXRT"): 91 | from machine import SDCard 92 | 93 | sd = SDCard(1) # Teensy 4.1: sck=45, mosi=43, miso=42, cs=44 94 | os.mount(sd, "/sd") 95 | 96 | # ======= I2S CONFIGURATION ======= 97 | SCK_PIN = 4 98 | WS_PIN = 3 99 | SD_PIN = 2 100 | I2S_ID = 2 101 | BUFFER_LENGTH_IN_BYTES = 40000 102 | # ======= I2S CONFIGURATION ======= 103 | 104 | else: 105 | print("Warning: program not tested with this board") 106 | 107 | # ======= AUDIO CONFIGURATION ======= 108 | WAV_FILE = "music-16k-16bits-mono.wav" 109 | WAV_SAMPLE_SIZE_IN_BITS = 16 110 | FORMAT = I2S.MONO 111 | SAMPLE_RATE_IN_HZ = 16000 112 | # ======= AUDIO CONFIGURATION ======= 113 | 114 | PLAY = 0 115 | PAUSE = 1 116 | RESUME = 2 117 | STOP = 3 118 | 119 | 120 | def eof_callback(arg): 121 | global state 122 | print("end of audio file") 123 | # state = STOP # uncomment to stop looping playback 124 | 125 | 126 | def i2s_callback(arg): 127 | global state 128 | if state == PLAY: 129 | num_read = wav.readinto(wav_samples_mv) 130 | # end of WAV file? 131 | if num_read == 0: 132 | # end-of-file, advance to first byte of Data section 133 | pos = wav.seek(44) 134 | _ = audio_out.write(silence) 135 | micropython.schedule(eof_callback, None) 136 | else: 137 | _ = audio_out.write(wav_samples_mv[:num_read]) 138 | elif state == RESUME: 139 | state = PLAY 140 | _ = audio_out.write(silence) 141 | elif state == PAUSE: 142 | _ = audio_out.write(silence) 143 | elif state == STOP: 144 | # cleanup 145 | wav.close() 146 | if os.uname().machine.count("PYBD"): 147 | os.umount("/sd") 148 | elif os.uname().machine.count("ESP32"): 149 | os.umount("/sd") 150 | sd.deinit() 151 | elif os.uname().machine.count("Raspberry"): 152 | os.umount("/sd") 153 | spi.deinit() 154 | elif os.uname().machine.count("MIMXRT"): 155 | os.umount("/sd") 156 | sd.deinit() 157 | audio_out.deinit() 158 | print("Done") 159 | else: 160 | print("Not a valid state. State ignored") 161 | 162 | 163 | audio_out = I2S( 164 | I2S_ID, 165 | sck=Pin(SCK_PIN), 166 | ws=Pin(WS_PIN), 167 | sd=Pin(SD_PIN), 168 | mode=I2S.TX, 169 | bits=WAV_SAMPLE_SIZE_IN_BITS, 170 | format=FORMAT, 171 | rate=SAMPLE_RATE_IN_HZ, 172 | ibuf=BUFFER_LENGTH_IN_BYTES, 173 | ) 174 | 175 | audio_out.irq(i2s_callback) 176 | state = PAUSE 177 | 178 | wav = open("/sd/{}".format(WAV_FILE), "rb") 179 | _ = wav.seek(44) # advance to first byte of Data section in WAV file 180 | 181 | # allocate a small array of blank samples 182 | silence = bytearray(1000) 183 | 184 | # allocate sample array buffer 185 | wav_samples = bytearray(10000) 186 | wav_samples_mv = memoryview(wav_samples) 187 | 188 | _ = audio_out.write(silence) 189 | 190 | # add runtime code here .... 191 | # changing 'state' will affect playback of audio file 192 | 193 | print("starting playback for 10s") 194 | state = PLAY 195 | time.sleep(10) 196 | print("pausing playback for 10s") 197 | state = PAUSE 198 | time.sleep(10) 199 | print("resuming playback for 15s") 200 | state = RESUME 201 | time.sleep(15) 202 | print("stopping playback") 203 | state = STOP 204 | -------------------------------------------------------------------------------- /examples/play_wav_from_sdcard_uasyncio.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # Copyright (c) 2022 Mike Teachman 3 | # https://opensource.org/licenses/MIT 4 | 5 | # Purpose: Play a WAV audio file out of a speaker or headphones 6 | # 7 | # - read audio samples from a WAV file on SD Card 8 | # - write audio samples to an I2S amplifier or DAC module 9 | # - the WAV file will play continuously in a loop until 10 | # a keyboard interrupt is detected or the board is reset 11 | # 12 | # uasyncio version 13 | 14 | import os 15 | import time 16 | import urandom 17 | import uasyncio as asyncio 18 | from machine import I2S 19 | from machine import Pin 20 | 21 | if os.uname().machine.count("PYBv1"): 22 | 23 | # ======= I2S CONFIGURATION ======= 24 | SCK_PIN = "Y6" 25 | WS_PIN = "Y5" 26 | SD_PIN = "Y8" 27 | I2S_ID = 2 28 | BUFFER_LENGTH_IN_BYTES = 40000 29 | # ======= I2S CONFIGURATION ======= 30 | 31 | elif os.uname().machine.count("PYBD"): 32 | import pyb 33 | 34 | pyb.Pin("EN_3V3").on() # provide 3.3V on 3V3 output pin 35 | os.mount(pyb.SDCard(), "/sd") 36 | 37 | # ======= I2S CONFIGURATION ======= 38 | SCK_PIN = "Y6" 39 | WS_PIN = "Y5" 40 | SD_PIN = "Y8" 41 | I2S_ID = 2 42 | BUFFER_LENGTH_IN_BYTES = 40000 43 | # ======= I2S CONFIGURATION ======= 44 | 45 | elif os.uname().machine.count("ESP32"): 46 | from machine import SDCard 47 | 48 | sd = SDCard(slot=2) # sck=18, mosi=23, miso=19, cs=5 49 | os.mount(sd, "/sd") 50 | 51 | # ======= I2S CONFIGURATION ======= 52 | SCK_PIN = 32 53 | WS_PIN = 25 54 | SD_PIN = 33 55 | I2S_ID = 0 56 | BUFFER_LENGTH_IN_BYTES = 40000 57 | # ======= I2S CONFIGURATION ======= 58 | 59 | elif os.uname().machine.count("Raspberry"): 60 | from sdcard import SDCard 61 | from machine import SPI 62 | 63 | cs = Pin(13, machine.Pin.OUT) 64 | spi = SPI( 65 | 1, 66 | baudrate=1_000_000, # this has no effect on spi bus speed to SD Card 67 | polarity=0, 68 | phase=0, 69 | bits=8, 70 | firstbit=machine.SPI.MSB, 71 | sck=Pin(14), 72 | mosi=Pin(15), 73 | miso=Pin(12), 74 | ) 75 | 76 | sd = SDCard(spi, cs) 77 | sd.init_spi(25_000_000) # increase SPI bus speed to SD card 78 | os.mount(sd, "/sd") 79 | 80 | # ======= I2S CONFIGURATION ======= 81 | SCK_PIN = 16 82 | WS_PIN = 17 83 | SD_PIN = 18 84 | I2S_ID = 0 85 | BUFFER_LENGTH_IN_BYTES = 40000 86 | # ======= I2S CONFIGURATION ======= 87 | 88 | elif os.uname().machine.count("MIMXRT"): 89 | from machine import SDCard 90 | 91 | sd = SDCard(1) # Teensy 4.1: sck=45, mosi=43, miso=42, cs=44 92 | os.mount(sd, "/sd") 93 | 94 | # ======= I2S CONFIGURATION ======= 95 | SCK_PIN = 4 96 | WS_PIN = 3 97 | SD_PIN = 2 98 | I2S_ID = 2 99 | BUFFER_LENGTH_IN_BYTES = 40000 100 | # ======= I2S CONFIGURATION ======= 101 | 102 | else: 103 | print("Warning: program not tested with this board") 104 | 105 | # ======= AUDIO CONFIGURATION ======= 106 | WAV_FILE = "music-16k-16bits-stereo.wav" 107 | WAV_SAMPLE_SIZE_IN_BITS = 16 108 | FORMAT = I2S.STEREO 109 | SAMPLE_RATE_IN_HZ = 16000 110 | # ======= AUDIO CONFIGURATION ======= 111 | 112 | 113 | async def continuous_play(audio_out, wav): 114 | swriter = asyncio.StreamWriter(audio_out) 115 | 116 | _ = wav.seek(44) # advance to first byte of Data section in WAV file 117 | 118 | # allocate sample array 119 | # memoryview used to reduce heap allocation 120 | wav_samples = bytearray(10000) 121 | wav_samples_mv = memoryview(wav_samples) 122 | 123 | # continuously read audio samples from the WAV file 124 | # and write them to an I2S DAC 125 | print("========== START PLAYBACK ==========") 126 | 127 | while True: 128 | num_read = wav.readinto(wav_samples_mv) 129 | # end of WAV file? 130 | if num_read == 0: 131 | # end-of-file, advance to first byte of Data section 132 | _ = wav.seek(44) 133 | else: 134 | # apply temporary workaround to eliminate heap allocation in uasyncio Stream class. 135 | # workaround can be removed after acceptance of PR: 136 | # https://github.com/micropython/micropython/pull/7868 137 | # swriter.write(wav_samples_mv[:num_read]) 138 | swriter.out_buf = wav_samples_mv[:num_read] 139 | await swriter.drain() 140 | 141 | 142 | async def another_task(name): 143 | while True: 144 | await asyncio.sleep(urandom.randrange(2, 5)) 145 | print("{} woke up".format(name)) 146 | time.sleep_ms(1) # simulates task doing something 147 | 148 | 149 | async def main(audio_out, wav): 150 | play = asyncio.create_task(continuous_play(audio_out, wav)) 151 | task_a = asyncio.create_task(another_task("task a")) 152 | task_b = asyncio.create_task(another_task("task b")) 153 | 154 | # keep the event loop active 155 | while True: 156 | await asyncio.sleep_ms(10) 157 | 158 | 159 | try: 160 | audio_out = I2S( 161 | I2S_ID, 162 | sck=Pin(SCK_PIN), 163 | ws=Pin(WS_PIN), 164 | sd=Pin(SD_PIN), 165 | mode=I2S.TX, 166 | bits=WAV_SAMPLE_SIZE_IN_BITS, 167 | format=FORMAT, 168 | rate=SAMPLE_RATE_IN_HZ, 169 | ibuf=BUFFER_LENGTH_IN_BYTES, 170 | ) 171 | 172 | wav = open("/sd/{}".format(WAV_FILE), "rb") 173 | asyncio.run(main(audio_out, wav)) 174 | except (KeyboardInterrupt, Exception) as e: 175 | print("Exception {} {}\n".format(type(e).__name__, e)) 176 | finally: 177 | # cleanup 178 | wav.close() 179 | if os.uname().machine.count("PYBD"): 180 | os.umount("/sd") 181 | elif os.uname().machine.count("ESP32"): 182 | os.umount("/sd") 183 | sd.deinit() 184 | elif os.uname().machine.count("Raspberry"): 185 | os.umount("/sd") 186 | spi.deinit() 187 | elif os.uname().machine.count("MIMXRT"): 188 | os.umount("/sd") 189 | sd.deinit() 190 | audio_out.deinit() 191 | ret = asyncio.new_event_loop() # Clear retained uasyncio state 192 | print("========== DONE PLAYBACK ==========") 193 | -------------------------------------------------------------------------------- /examples/record_mic_to_sdcard_blocking.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # Copyright (c) 2022 Mike Teachman 3 | # https://opensource.org/licenses/MIT 4 | 5 | # Purpose: Read audio samples from an I2S microphone and write to SD card 6 | # 7 | # - read 32-bit audio samples from I2S hardware, typically an I2S MEMS Microphone 8 | # - convert 32-bit samples to specified bit size 9 | # - write samples to a SD card file in WAV format 10 | # - samples will be continuously written to the WAV file 11 | # until a keyboard interrupt (ctrl-c) is detected 12 | # 13 | # Blocking version 14 | # - the readinto() method blocks until 15 | # the supplied buffer is filled 16 | 17 | import os 18 | from machine import Pin 19 | from machine import I2S 20 | 21 | if os.uname().machine.count("PYBv1"): 22 | 23 | # ======= I2S CONFIGURATION ======= 24 | SCK_PIN = "Y6" 25 | WS_PIN = "Y5" 26 | SD_PIN = "Y8" 27 | I2S_ID = 2 28 | BUFFER_LENGTH_IN_BYTES = 40000 29 | # ======= I2S CONFIGURATION ======= 30 | 31 | elif os.uname().machine.count("PYBD"): 32 | import pyb 33 | 34 | pyb.Pin("EN_3V3").on() # provide 3.3V on 3V3 output pin 35 | os.mount(pyb.SDCard(), "/sd") 36 | 37 | # ======= I2S CONFIGURATION ======= 38 | SCK_PIN = "Y6" 39 | WS_PIN = "Y5" 40 | SD_PIN = "Y8" 41 | I2S_ID = 2 42 | BUFFER_LENGTH_IN_BYTES = 40000 43 | # ======= I2S CONFIGURATION ======= 44 | 45 | elif os.uname().machine.count("ESP32"): 46 | from machine import SDCard 47 | 48 | sd = SDCard(slot=2) # sck=18, mosi=23, miso=19, cs=5 49 | os.mount(sd, "/sd") 50 | 51 | # ======= I2S CONFIGURATION ======= 52 | SCK_PIN = 32 53 | WS_PIN = 25 54 | SD_PIN = 33 55 | I2S_ID = 0 56 | BUFFER_LENGTH_IN_BYTES = 40000 57 | # ======= I2S CONFIGURATION ======= 58 | 59 | elif os.uname().machine.count("Raspberry"): 60 | from sdcard import SDCard 61 | from machine import SPI 62 | 63 | cs = Pin(13, machine.Pin.OUT) 64 | spi = SPI( 65 | 1, 66 | baudrate=1_000_000, # this has no effect on spi bus speed to SD Card 67 | polarity=0, 68 | phase=0, 69 | bits=8, 70 | firstbit=machine.SPI.MSB, 71 | sck=Pin(14), 72 | mosi=Pin(15), 73 | miso=Pin(12), 74 | ) 75 | 76 | sd = SDCard(spi, cs) 77 | sd.init_spi(25_000_000) # increase SPI bus speed to SD card 78 | os.mount(sd, "/sd") 79 | 80 | # ======= I2S CONFIGURATION ======= 81 | SCK_PIN = 16 82 | WS_PIN = 17 83 | SD_PIN = 18 84 | I2S_ID = 0 85 | BUFFER_LENGTH_IN_BYTES = 60000 # larger buffer to accommodate slow SD card driver 86 | # ======= I2S CONFIGURATION ======= 87 | 88 | elif os.uname().machine.count("MIMXRT"): 89 | from machine import SDCard 90 | 91 | sd = SDCard(1) # Teensy 4.1: sck=45, mosi=43, miso=42, cs=44 92 | os.mount(sd, "/sd") 93 | 94 | # ======= I2S CONFIGURATION ======= 95 | SCK_PIN = 21 96 | WS_PIN = 20 97 | SD_PIN = 8 98 | I2S_ID = 1 99 | BUFFER_LENGTH_IN_BYTES = 40000 100 | # ======= I2S CONFIGURATION ======= 101 | 102 | else: 103 | print("Warning: program not tested with this board") 104 | 105 | # ======= AUDIO CONFIGURATION ======= 106 | WAV_FILE = "mic.wav" 107 | RECORD_TIME_IN_SECONDS = 10 108 | WAV_SAMPLE_SIZE_IN_BITS = 16 109 | FORMAT = I2S.MONO 110 | SAMPLE_RATE_IN_HZ = 22_050 111 | # ======= AUDIO CONFIGURATION ======= 112 | 113 | format_to_channels = {I2S.MONO: 1, I2S.STEREO: 2} 114 | NUM_CHANNELS = format_to_channels[FORMAT] 115 | WAV_SAMPLE_SIZE_IN_BYTES = WAV_SAMPLE_SIZE_IN_BITS // 8 116 | RECORDING_SIZE_IN_BYTES = ( 117 | RECORD_TIME_IN_SECONDS * SAMPLE_RATE_IN_HZ * WAV_SAMPLE_SIZE_IN_BYTES * NUM_CHANNELS 118 | ) 119 | 120 | 121 | def create_wav_header(sampleRate, bitsPerSample, num_channels, num_samples): 122 | datasize = num_samples * num_channels * bitsPerSample // 8 123 | o = bytes("RIFF", "ascii") # (4byte) Marks file as RIFF 124 | o += (datasize + 36).to_bytes( 125 | 4, "little" 126 | ) # (4byte) File size in bytes excluding this and RIFF marker 127 | o += bytes("WAVE", "ascii") # (4byte) File type 128 | o += bytes("fmt ", "ascii") # (4byte) Format Chunk Marker 129 | o += (16).to_bytes(4, "little") # (4byte) Length of above format data 130 | o += (1).to_bytes(2, "little") # (2byte) Format type (1 - PCM) 131 | o += (num_channels).to_bytes(2, "little") # (2byte) 132 | o += (sampleRate).to_bytes(4, "little") # (4byte) 133 | o += (sampleRate * num_channels * bitsPerSample // 8).to_bytes(4, "little") # (4byte) 134 | o += (num_channels * bitsPerSample // 8).to_bytes(2, "little") # (2byte) 135 | o += (bitsPerSample).to_bytes(2, "little") # (2byte) 136 | o += bytes("data", "ascii") # (4byte) Data Chunk Marker 137 | o += (datasize).to_bytes(4, "little") # (4byte) Data size in bytes 138 | return o 139 | 140 | 141 | wav = open("/sd/{}".format(WAV_FILE), "wb") 142 | 143 | # create header for WAV file and write to SD card 144 | wav_header = create_wav_header( 145 | SAMPLE_RATE_IN_HZ, 146 | WAV_SAMPLE_SIZE_IN_BITS, 147 | NUM_CHANNELS, 148 | SAMPLE_RATE_IN_HZ * RECORD_TIME_IN_SECONDS, 149 | ) 150 | num_bytes_written = wav.write(wav_header) 151 | 152 | audio_in = I2S( 153 | I2S_ID, 154 | sck=Pin(SCK_PIN), 155 | ws=Pin(WS_PIN), 156 | sd=Pin(SD_PIN), 157 | mode=I2S.RX, 158 | bits=WAV_SAMPLE_SIZE_IN_BITS, 159 | format=FORMAT, 160 | rate=SAMPLE_RATE_IN_HZ, 161 | ibuf=BUFFER_LENGTH_IN_BYTES, 162 | ) 163 | 164 | # allocate sample arrays 165 | # memoryview used to reduce heap allocation in while loop 166 | mic_samples = bytearray(10000) 167 | mic_samples_mv = memoryview(mic_samples) 168 | 169 | num_sample_bytes_written_to_wav = 0 170 | 171 | print("Recording size: {} bytes".format(RECORDING_SIZE_IN_BYTES)) 172 | print("========== START RECORDING ==========") 173 | try: 174 | while num_sample_bytes_written_to_wav < RECORDING_SIZE_IN_BYTES: 175 | # read a block of samples from the I2S microphone 176 | num_bytes_read_from_mic = audio_in.readinto(mic_samples_mv) 177 | if num_bytes_read_from_mic > 0: 178 | num_bytes_to_write = min( 179 | num_bytes_read_from_mic, RECORDING_SIZE_IN_BYTES - num_sample_bytes_written_to_wav 180 | ) 181 | # write samples to WAV file 182 | num_bytes_written = wav.write(mic_samples_mv[:num_bytes_to_write]) 183 | num_sample_bytes_written_to_wav += num_bytes_written 184 | 185 | print("========== DONE RECORDING ==========") 186 | except (KeyboardInterrupt, Exception) as e: 187 | print("caught exception {} {}".format(type(e).__name__, e)) 188 | 189 | # cleanup 190 | wav.close() 191 | if os.uname().machine.count("PYBD"): 192 | os.umount("/sd") 193 | elif os.uname().machine.count("ESP32"): 194 | os.umount("/sd") 195 | sd.deinit() 196 | elif os.uname().machine.count("Raspberry"): 197 | os.umount("/sd") 198 | spi.deinit() 199 | elif os.uname().machine.count("MIMXRT"): 200 | os.umount("/sd") 201 | sd.deinit() 202 | audio_in.deinit() 203 | -------------------------------------------------------------------------------- /examples/record_mic_to_sdcard_non_blocking.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # Copyright (c) 2022 Mike Teachman 3 | # https://opensource.org/licenses/MIT 4 | 5 | # Purpose: Read audio samples from an I2S microphone and write to SD card 6 | # 7 | # - read 32-bit audio samples from I2S hardware, typically an I2S MEMS Microphone 8 | # - convert 32-bit audio samples to specified bit size 9 | # - write samples to a SD card file in WAV format 10 | # - samples will be continuously written to the WAV file 11 | # until the 'state" variable is changed to 'STOP' 12 | # 13 | # Non-Blocking version 14 | # - the readinto() method does not block. A callback function 15 | # is called when the buffer supplied to read_into() is filled 16 | 17 | import os 18 | import time 19 | from machine import Pin 20 | from machine import I2S 21 | 22 | if os.uname().machine.count("PYBv1"): 23 | 24 | # ======= I2S CONFIGURATION ======= 25 | SCK_PIN = "Y6" 26 | WS_PIN = "Y5" 27 | SD_PIN = "Y8" 28 | I2S_ID = 2 29 | BUFFER_LENGTH_IN_BYTES = 40000 30 | # ======= I2S CONFIGURATION ======= 31 | 32 | elif os.uname().machine.count("PYBD"): 33 | import pyb 34 | 35 | pyb.Pin("EN_3V3").on() # provide 3.3V on 3V3 output pin 36 | os.mount(pyb.SDCard(), "/sd") 37 | 38 | # ======= I2S CONFIGURATION ======= 39 | SCK_PIN = "Y6" 40 | WS_PIN = "Y5" 41 | SD_PIN = "Y8" 42 | I2S_ID = 2 43 | BUFFER_LENGTH_IN_BYTES = 40000 44 | # ======= I2S CONFIGURATION ======= 45 | 46 | elif os.uname().machine.count("ESP32"): 47 | from machine import SDCard 48 | 49 | sd = SDCard(slot=2) # sck=18, mosi=23, miso=19, cs=5 50 | os.mount(sd, "/sd") 51 | 52 | # ======= I2S CONFIGURATION ======= 53 | SCK_PIN = 32 54 | WS_PIN = 25 55 | SD_PIN = 33 56 | I2S_ID = 0 57 | BUFFER_LENGTH_IN_BYTES = 40000 58 | # ======= I2S CONFIGURATION ======= 59 | 60 | elif os.uname().machine.count("Raspberry"): 61 | from sdcard import SDCard 62 | from machine import SPI 63 | 64 | cs = Pin(13, machine.Pin.OUT) 65 | spi = SPI( 66 | 1, 67 | baudrate=1_000_000, # this has no effect on spi bus speed to SD Card 68 | polarity=0, 69 | phase=0, 70 | bits=8, 71 | firstbit=machine.SPI.MSB, 72 | sck=Pin(14), 73 | mosi=Pin(15), 74 | miso=Pin(12), 75 | ) 76 | 77 | sd = SDCard(spi, cs) 78 | sd.init_spi(25_000_000) # increase SPI bus speed to SD card 79 | os.mount(sd, "/sd") 80 | 81 | # ======= I2S CONFIGURATION ======= 82 | SCK_PIN = 16 83 | WS_PIN = 17 84 | SD_PIN = 18 85 | I2S_ID = 0 86 | BUFFER_LENGTH_IN_BYTES = 60000 # larger buffer to accommodate slow SD card driver 87 | # ======= I2S CONFIGURATION ======= 88 | 89 | elif os.uname().machine.count("MIMXRT"): 90 | from machine import SDCard 91 | 92 | sd = SDCard(1) # Teensy 4.1: sck=45, mosi=43, miso=42, cs=44 93 | os.mount(sd, "/sd") 94 | 95 | # ======= I2S CONFIGURATION ======= 96 | SCK_PIN = 21 97 | WS_PIN = 20 98 | SD_PIN = 8 99 | I2S_ID = 1 100 | BUFFER_LENGTH_IN_BYTES = 40000 101 | # ======= I2S CONFIGURATION ======= 102 | 103 | else: 104 | print("Warning: program not tested with this board") 105 | 106 | # ======= AUDIO CONFIGURATION ======= 107 | WAV_FILE = "mic.wav" 108 | WAV_SAMPLE_SIZE_IN_BITS = 16 109 | FORMAT = I2S.MONO 110 | SAMPLE_RATE_IN_HZ = 22050 111 | # ======= AUDIO CONFIGURATION ======= 112 | 113 | RECORD = 0 114 | PAUSE = 1 115 | RESUME = 2 116 | STOP = 3 117 | 118 | format_to_channels = {I2S.MONO: 1, I2S.STEREO: 2} 119 | NUM_CHANNELS = format_to_channels[FORMAT] 120 | WAV_SAMPLE_SIZE_IN_BYTES = WAV_SAMPLE_SIZE_IN_BITS // 8 121 | 122 | 123 | def create_wav_header(sampleRate, bitsPerSample, num_channels, num_samples): 124 | datasize = num_samples * num_channels * bitsPerSample // 8 125 | o = bytes("RIFF", "ascii") # (4byte) Marks file as RIFF 126 | o += (datasize + 36).to_bytes( 127 | 4, "little" 128 | ) # (4byte) File size in bytes excluding this and RIFF marker 129 | o += bytes("WAVE", "ascii") # (4byte) File type 130 | o += bytes("fmt ", "ascii") # (4byte) Format Chunk Marker 131 | o += (16).to_bytes(4, "little") # (4byte) Length of above format data 132 | o += (1).to_bytes(2, "little") # (2byte) Format type (1 - PCM) 133 | o += (num_channels).to_bytes(2, "little") # (2byte) 134 | o += (sampleRate).to_bytes(4, "little") # (4byte) 135 | o += (sampleRate * num_channels * bitsPerSample // 8).to_bytes(4, "little") # (4byte) 136 | o += (num_channels * bitsPerSample // 8).to_bytes(2, "little") # (2byte) 137 | o += (bitsPerSample).to_bytes(2, "little") # (2byte) 138 | o += bytes("data", "ascii") # (4byte) Data Chunk Marker 139 | o += (datasize).to_bytes(4, "little") # (4byte) Data size in bytes 140 | return o 141 | 142 | 143 | def i2s_callback_rx(arg): 144 | global state 145 | global num_sample_bytes_written_to_wav 146 | global mic_samples_mv 147 | global num_read 148 | 149 | if state == RECORD: 150 | num_bytes_written = wav.write(mic_samples_mv[:num_read]) 151 | num_sample_bytes_written_to_wav += num_bytes_written 152 | # read samples from the I2S device. This callback function 153 | # will be called after 'mic_samples_mv' has been completely filled 154 | # with audio samples 155 | num_read = audio_in.readinto(mic_samples_mv) 156 | elif state == RESUME: 157 | state = RECORD 158 | num_read = audio_in.readinto(mic_samples_mv) 159 | elif state == PAUSE: 160 | # in the PAUSE state read audio samples from the I2S device 161 | # but do not write the samples to SD card 162 | num_read = audio_in.readinto(mic_samples_mv) 163 | elif state == STOP: 164 | # create header for WAV file and write to SD card 165 | wav_header = create_wav_header( 166 | SAMPLE_RATE_IN_HZ, 167 | WAV_SAMPLE_SIZE_IN_BITS, 168 | NUM_CHANNELS, 169 | num_sample_bytes_written_to_wav // (WAV_SAMPLE_SIZE_IN_BYTES * NUM_CHANNELS), 170 | ) 171 | _ = wav.seek(0) # advance to first byte of Header section in WAV file 172 | num_bytes_written = wav.write(wav_header) 173 | # cleanup 174 | wav.close() 175 | if os.uname().machine.count("PYBD"): 176 | os.umount("/sd") 177 | elif os.uname().machine.count("ESP32"): 178 | os.umount("/sd") 179 | sd.deinit() 180 | elif os.uname().machine.count("Raspberry"): 181 | os.umount("/sd") 182 | spi.deinit() 183 | elif os.uname().machine.count("MIMXRT"): 184 | os.umount("/sd") 185 | sd.deinit() 186 | audio_in.deinit() 187 | print("Done") 188 | else: 189 | print("Not a valid state. State ignored") 190 | 191 | 192 | wav = open("/sd/{}".format(WAV_FILE), "wb") 193 | pos = wav.seek(44) # advance to first byte of Data section in WAV file 194 | 195 | audio_in = I2S( 196 | I2S_ID, 197 | sck=Pin(SCK_PIN), 198 | ws=Pin(WS_PIN), 199 | sd=Pin(SD_PIN), 200 | mode=I2S.RX, 201 | bits=WAV_SAMPLE_SIZE_IN_BITS, 202 | format=FORMAT, 203 | rate=SAMPLE_RATE_IN_HZ, 204 | ibuf=BUFFER_LENGTH_IN_BYTES, 205 | ) 206 | 207 | # setting a callback function makes the 208 | # readinto() method Non-Blocking 209 | audio_in.irq(i2s_callback_rx) 210 | 211 | # allocate sample arrays 212 | # memoryview used to reduce heap allocation in while loop 213 | mic_samples = bytearray(10000) 214 | mic_samples_mv = memoryview(mic_samples) 215 | 216 | num_sample_bytes_written_to_wav = 0 217 | 218 | state = PAUSE 219 | # start the background activity to read the microphone. 220 | # the callback will keep the activity continually running in the background. 221 | num_read = audio_in.readinto(mic_samples_mv) 222 | 223 | # === Main program code goes here === 224 | # audio sample recording to SD card will be running in the background 225 | # changing 'state' can cause the recording to Pause, Resume, or Stop 226 | 227 | print("starting recording for 5s") 228 | state = RECORD 229 | time.sleep(5) 230 | print("pausing recording for 2s") 231 | state = PAUSE 232 | time.sleep(2) 233 | print("resuming recording for 5s") 234 | state = RESUME 235 | time.sleep(5) 236 | print("stopping recording and closing WAV file") 237 | state = STOP 238 | -------------------------------------------------------------------------------- /examples/record_mic_to_sdcard_uasyncio.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # Copyright (c) 2022 Mike Teachman 3 | # https://opensource.org/licenses/MIT 4 | 5 | # Purpose: Read audio samples from an I2S microphone and write to SD card 6 | # 7 | # - read 32-bit audio samples from I2S hardware, typically an I2S MEMS Microphone 8 | # - convert 32-bit samples to specified bit size and format 9 | # - write samples to a SD card file in WAV format 10 | # - samples will be continuously written to the WAV file 11 | # for the specified amount of time 12 | # 13 | # uasyncio version 14 | 15 | import os 16 | import time 17 | import urandom 18 | import uasyncio as asyncio 19 | from machine import I2S 20 | from machine import Pin 21 | 22 | if os.uname().machine.count("PYBv1"): 23 | 24 | # ======= I2S CONFIGURATION ======= 25 | SCK_PIN = "Y6" 26 | WS_PIN = "Y5" 27 | SD_PIN = "Y8" 28 | I2S_ID = 2 29 | BUFFER_LENGTH_IN_BYTES = 40000 30 | # ======= I2S CONFIGURATION ======= 31 | 32 | elif os.uname().machine.count("PYBD"): 33 | import pyb 34 | 35 | pyb.Pin("EN_3V3").on() # provide 3.3V on 3V3 output pin 36 | os.mount(pyb.SDCard(), "/sd") 37 | 38 | # ======= I2S CONFIGURATION ======= 39 | SCK_PIN = "Y6" 40 | WS_PIN = "Y5" 41 | SD_PIN = "Y8" 42 | I2S_ID = 2 43 | BUFFER_LENGTH_IN_BYTES = 40000 44 | # ======= I2S CONFIGURATION ======= 45 | 46 | elif os.uname().machine.count("ESP32"): 47 | from machine import SDCard 48 | 49 | sd = SDCard(slot=2) # sck=18, mosi=23, miso=19, cs=5 50 | os.mount(sd, "/sd") 51 | 52 | # ======= I2S CONFIGURATION ======= 53 | SCK_PIN = 32 54 | WS_PIN = 25 55 | SD_PIN = 33 56 | I2S_ID = 0 57 | BUFFER_LENGTH_IN_BYTES = 40000 58 | # ======= I2S CONFIGURATION ======= 59 | 60 | elif os.uname().machine.count("Raspberry"): 61 | from sdcard import SDCard 62 | from machine import SPI 63 | 64 | cs = Pin(13, machine.Pin.OUT) 65 | spi = SPI( 66 | 1, 67 | baudrate=1_000_000, # this has no effect on spi bus speed to SD Card 68 | polarity=0, 69 | phase=0, 70 | bits=8, 71 | firstbit=machine.SPI.MSB, 72 | sck=Pin(14), 73 | mosi=Pin(15), 74 | miso=Pin(12), 75 | ) 76 | 77 | sd = SDCard(spi, cs) 78 | sd.init_spi(25_000_000) # increase SPI bus speed to SD card 79 | os.mount(sd, "/sd") 80 | 81 | # ======= I2S CONFIGURATION ======= 82 | SCK_PIN = 16 83 | WS_PIN = 17 84 | SD_PIN = 18 85 | I2S_ID = 0 86 | BUFFER_LENGTH_IN_BYTES = 60000 # larger buffer to accommodate slow SD card driver 87 | # ======= I2S CONFIGURATION ======= 88 | 89 | elif os.uname().machine.count("MIMXRT"): 90 | from machine import SDCard 91 | 92 | sd = SDCard(1) # Teensy 4.1: sck=45, mosi=43, miso=42, cs=44 93 | os.mount(sd, "/sd") 94 | 95 | # ======= I2S CONFIGURATION ======= 96 | SCK_PIN = 21 97 | WS_PIN = 20 98 | SD_PIN = 8 99 | I2S_ID = 1 100 | BUFFER_LENGTH_IN_BYTES = 40000 101 | # ======= I2S CONFIGURATION ======= 102 | 103 | else: 104 | print("Warning: program not tested with this board") 105 | 106 | # ======= AUDIO CONFIGURATION ======= 107 | WAV_FILE = "mic.wav" 108 | RECORD_TIME_IN_SECONDS = 10 109 | WAV_SAMPLE_SIZE_IN_BITS = 16 110 | FORMAT = I2S.MONO 111 | SAMPLE_RATE_IN_HZ = 22050 112 | # ======= AUDIO CONFIGURATION ======= 113 | 114 | format_to_channels = {I2S.MONO: 1, I2S.STEREO: 2} 115 | NUM_CHANNELS = format_to_channels[FORMAT] 116 | WAV_SAMPLE_SIZE_IN_BYTES = WAV_SAMPLE_SIZE_IN_BITS // 8 117 | RECORDING_SIZE_IN_BYTES = ( 118 | RECORD_TIME_IN_SECONDS * SAMPLE_RATE_IN_HZ * WAV_SAMPLE_SIZE_IN_BYTES * NUM_CHANNELS 119 | ) 120 | 121 | 122 | def create_wav_header(sampleRate, bitsPerSample, num_channels, num_samples): 123 | datasize = num_samples * num_channels * bitsPerSample // 8 124 | o = bytes("RIFF", "ascii") # (4byte) Marks file as RIFF 125 | o += (datasize + 36).to_bytes( 126 | 4, "little" 127 | ) # (4byte) File size in bytes excluding this and RIFF marker 128 | o += bytes("WAVE", "ascii") # (4byte) File type 129 | o += bytes("fmt ", "ascii") # (4byte) Format Chunk Marker 130 | o += (16).to_bytes(4, "little") # (4byte) Length of above format data 131 | o += (1).to_bytes(2, "little") # (2byte) Format type (1 - PCM) 132 | o += (num_channels).to_bytes(2, "little") # (2byte) 133 | o += (sampleRate).to_bytes(4, "little") # (4byte) 134 | o += (sampleRate * num_channels * bitsPerSample // 8).to_bytes(4, "little") # (4byte) 135 | o += (num_channels * bitsPerSample // 8).to_bytes(2, "little") # (2byte) 136 | o += (bitsPerSample).to_bytes(2, "little") # (2byte) 137 | o += bytes("data", "ascii") # (4byte) Data Chunk Marker 138 | o += (datasize).to_bytes(4, "little") # (4byte) Data size in bytes 139 | return o 140 | 141 | 142 | async def record_wav_to_sdcard(audio_in, wav): 143 | sreader = asyncio.StreamReader(audio_in) 144 | 145 | # create header for WAV file and write to SD card 146 | wav_header = create_wav_header( 147 | SAMPLE_RATE_IN_HZ, 148 | WAV_SAMPLE_SIZE_IN_BITS, 149 | NUM_CHANNELS, 150 | SAMPLE_RATE_IN_HZ * RECORD_TIME_IN_SECONDS, 151 | ) 152 | num_bytes_written = wav.write(wav_header) 153 | 154 | # allocate sample array 155 | # memoryview used to reduce heap allocation 156 | mic_samples = bytearray(10000) 157 | mic_samples_mv = memoryview(mic_samples) 158 | 159 | num_sample_bytes_written_to_wav = 0 160 | 161 | # continuously read audio samples from I2S hardware 162 | # and write them to a WAV file stored on a SD card 163 | print("Recording size: {} bytes".format(RECORDING_SIZE_IN_BYTES)) 164 | print("========== START RECORDING ==========") 165 | while num_sample_bytes_written_to_wav < RECORDING_SIZE_IN_BYTES: 166 | # read samples from the I2S peripheral 167 | num_bytes_read_from_mic = await sreader.readinto(mic_samples_mv) 168 | # write samples to WAV file 169 | if num_bytes_read_from_mic > 0: 170 | num_bytes_to_write = min( 171 | num_bytes_read_from_mic, RECORDING_SIZE_IN_BYTES - num_sample_bytes_written_to_wav 172 | ) 173 | num_bytes_written = wav.write(mic_samples_mv[:num_bytes_to_write]) 174 | num_sample_bytes_written_to_wav += num_bytes_written 175 | 176 | print("========== DONE RECORDING ==========") 177 | # cleanup 178 | wav.close() 179 | if os.uname().machine.count("PYBD"): 180 | os.umount("/sd") 181 | elif os.uname().machine.count("ESP32"): 182 | os.umount("/sd") 183 | sd.deinit() 184 | elif os.uname().machine.count("Raspberry"): 185 | os.umount("/sd") 186 | spi.deinit() 187 | elif os.uname().machine.count("MIMXRT"): 188 | os.umount("/sd") 189 | sd.deinit() 190 | audio_in.deinit() 191 | 192 | 193 | async def another_task(name): 194 | while True: 195 | await asyncio.sleep(urandom.randrange(2, 5)) 196 | print("{} woke up".format(name)) 197 | time.sleep_ms(10) # simulates task doing something 198 | 199 | 200 | async def main(audio_in, wav): 201 | play = asyncio.create_task(record_wav_to_sdcard(audio_in, wav)) 202 | task_a = asyncio.create_task(another_task("task a")) 203 | task_b = asyncio.create_task(another_task("task b")) 204 | 205 | # keep the event loop active 206 | while True: 207 | await asyncio.sleep_ms(10) 208 | 209 | 210 | try: 211 | audio_in = I2S( 212 | I2S_ID, 213 | sck=Pin(SCK_PIN), 214 | ws=Pin(WS_PIN), 215 | sd=Pin(SD_PIN), 216 | mode=I2S.RX, 217 | bits=WAV_SAMPLE_SIZE_IN_BITS, 218 | format=FORMAT, 219 | rate=SAMPLE_RATE_IN_HZ, 220 | ibuf=BUFFER_LENGTH_IN_BYTES, 221 | ) 222 | 223 | wav = open("/sd/{}".format(WAV_FILE), "wb") 224 | asyncio.run(main(audio_in, wav)) 225 | except (KeyboardInterrupt, Exception) as e: 226 | print("Exception {} {}\n".format(type(e).__name__, e)) 227 | finally: 228 | # cleanup 229 | wav.close() 230 | if os.uname().machine.count("PYBD"): 231 | os.umount("/sd") 232 | elif os.uname().machine.count("ESP32"): 233 | os.umount("/sd") 234 | sd.deinit() 235 | elif os.uname().machine.count("Raspberry"): 236 | os.umount("/sd") 237 | spi.deinit() 238 | elif os.uname().machine.count("MIMXRT"): 239 | os.umount("/sd") 240 | sd.deinit() 241 | audio_in.deinit() 242 | ret = asyncio.new_event_loop() # Clear retained uasyncio state 243 | -------------------------------------------------------------------------------- /examples/wavplayer.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # Copyright (c) 2022 Mike Teachman 3 | # https://opensource.org/licenses/MIT 4 | # 5 | # MicroPython Class used to control playing a WAV file using an I2S amplifier or DAC module 6 | # - control playback with 5 methods: 7 | # - play() 8 | # - pause() 9 | # - resume() 10 | # - stop() 11 | # - isplaying() 12 | # Example: 13 | # wp = WavPlayer(id=I2S_ID, 14 | # sck_pin=Pin(SCK_PIN), 15 | # ws_pin=Pin(WS_PIN), 16 | # sd_pin=Pin(SD_PIN), 17 | # ibuf=BUFFER_LENGTH_IN_BYTES) 18 | # wp.play("YOUR_WAV_FILE.wav", loop=True) 19 | # 20 | # All methods are non-blocking. 21 | # The WAV file header is parsed in the play() method to get audio parameters 22 | 23 | import os 24 | import struct 25 | from machine import I2S 26 | 27 | 28 | class WavPlayer: 29 | PLAY = 0 30 | PAUSE = 1 31 | RESUME = 2 32 | FLUSH = 3 33 | STOP = 4 34 | 35 | def __init__(self, id, sck_pin, ws_pin, sd_pin, ibuf, root="/sd"): 36 | self.id = id 37 | self.sck_pin = sck_pin 38 | self.ws_pin = ws_pin 39 | self.sd_pin = sd_pin 40 | self.ibuf = ibuf 41 | self.root = root.rstrip("/") + "/" 42 | self.state = WavPlayer.STOP 43 | self.wav = None 44 | self.loop = False 45 | self.format = None 46 | self.sample_rate = None 47 | self.bits_per_sample = None 48 | self.first_sample_offset = None 49 | self.num_read = 0 50 | self.sbuf = 1000 51 | self.nflush = 0 52 | 53 | # allocate a small array of blank audio samples used for silence 54 | self.silence_samples = bytearray(self.sbuf) 55 | 56 | # allocate audio sample array buffer 57 | self.wav_samples_mv = memoryview(bytearray(10000)) 58 | 59 | def i2s_callback(self, arg): 60 | if self.state == WavPlayer.PLAY: 61 | self.num_read = self.wav.readinto(self.wav_samples_mv) 62 | # end of WAV file? 63 | if self.num_read == 0: 64 | # end-of-file 65 | if self.loop == False: 66 | self.state = WavPlayer.FLUSH 67 | else: 68 | # advance to first byte of Data section 69 | _ = self.wav.seek(self.first_sample_offset) 70 | _ = self.audio_out.write(self.silence_samples) 71 | else: 72 | _ = self.audio_out.write(self.wav_samples_mv[: self.num_read]) 73 | elif self.state == WavPlayer.RESUME: 74 | self.state = WavPlayer.PLAY 75 | _ = self.audio_out.write(self.silence_samples) 76 | elif self.state == WavPlayer.PAUSE: 77 | _ = self.audio_out.write(self.silence_samples) 78 | elif self.state == WavPlayer.FLUSH: 79 | # Flush is used to allow the residual audio samples in the 80 | # internal buffer to be written to the I2S peripheral. This step 81 | # avoids part of the sound file from being cut off 82 | if self.nflush > 0: 83 | self.nflush -= 1 84 | _ = self.audio_out.write(self.silence_samples) 85 | else: 86 | self.wav.close() 87 | self.audio_out.deinit() 88 | self.state = WavPlayer.STOP 89 | elif self.state == WavPlayer.STOP: 90 | pass 91 | else: 92 | raise SystemError("Internal error: unexpected state") 93 | self.state == WavPlayer.STOP 94 | 95 | def parse(self, wav_file): 96 | chunk_ID = wav_file.read(4) 97 | if chunk_ID != b"RIFF": 98 | raise ValueError("WAV chunk ID invalid") 99 | chunk_size = wav_file.read(4) 100 | format = wav_file.read(4) 101 | if format != b"WAVE": 102 | raise ValueError("WAV format invalid") 103 | sub_chunk1_ID = wav_file.read(4) 104 | if sub_chunk1_ID != b"fmt ": 105 | raise ValueError("WAV sub chunk 1 ID invalid") 106 | sub_chunk1_size = wav_file.read(4) 107 | audio_format = struct.unpack("WAV converters add 122 | # binary data before "data". So, read a fairly large 123 | # block of bytes and search for "data". 124 | 125 | binary_block = wav_file.read(200) 126 | offset = binary_block.find(b"data") 127 | if offset == -1: 128 | raise ValueError("WAV sub chunk 2 ID not found") 129 | 130 | self.first_sample_offset = 44 + offset 131 | 132 | def play(self, wav_file, loop=False): 133 | if os.listdir(self.root).count(wav_file) == 0: 134 | raise ValueError("%s: not found" % wav_file) 135 | if self.state == WavPlayer.PLAY: 136 | raise ValueError("already playing a WAV file") 137 | elif self.state == WavPlayer.PAUSE: 138 | raise ValueError("paused while playing a WAV file") 139 | else: 140 | self.wav = open(self.root + wav_file, "rb") 141 | self.loop = loop 142 | self.parse(self.wav) 143 | 144 | self.audio_out = I2S( 145 | self.id, 146 | sck=self.sck_pin, 147 | ws=self.ws_pin, 148 | sd=self.sd_pin, 149 | mode=I2S.TX, 150 | bits=self.bits_per_sample, 151 | format=self.format, 152 | rate=self.sample_rate, 153 | ibuf=self.ibuf, 154 | ) 155 | 156 | # advance to first byte of Data section in WAV file 157 | _ = self.wav.seek(self.first_sample_offset) 158 | self.audio_out.irq(self.i2s_callback) 159 | self.nflush = self.ibuf // self.sbuf + 1 160 | self.state = WavPlayer.PLAY 161 | _ = self.audio_out.write(self.silence_samples) 162 | 163 | def resume(self): 164 | if self.state != WavPlayer.PAUSE: 165 | raise ValueError("can only resume when WAV file is paused") 166 | else: 167 | self.state = WavPlayer.RESUME 168 | 169 | def pause(self): 170 | if self.state == WavPlayer.PAUSE: 171 | pass 172 | elif self.state != WavPlayer.PLAY: 173 | raise ValueError("can only pause when WAV file is playing") 174 | 175 | self.state = WavPlayer.PAUSE 176 | 177 | def stop(self): 178 | self.state = WavPlayer.FLUSH 179 | 180 | def isplaying(self): 181 | if self.state != WavPlayer.STOP: 182 | return True 183 | else: 184 | return False 185 | -------------------------------------------------------------------------------- /images/bucket.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miketeachman/micropython-i2s-examples/baf7b6412fe97bbf74a5b024f3985a517ec43f49/images/bucket.jpg -------------------------------------------------------------------------------- /images/esp32_mic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miketeachman/micropython-i2s-examples/baf7b6412fe97bbf74a5b024f3985a517ec43f49/images/esp32_mic.jpg -------------------------------------------------------------------------------- /images/esp32_uda.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miketeachman/micropython-i2s-examples/baf7b6412fe97bbf74a5b024f3985a517ec43f49/images/esp32_uda.jpg -------------------------------------------------------------------------------- /images/header_pins.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miketeachman/micropython-i2s-examples/baf7b6412fe97bbf74a5b024f3985a517ec43f49/images/header_pins.jpg -------------------------------------------------------------------------------- /images/jumper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miketeachman/micropython-i2s-examples/baf7b6412fe97bbf74a5b024f3985a517ec43f49/images/jumper.jpg -------------------------------------------------------------------------------- /images/pybd_mic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miketeachman/micropython-i2s-examples/baf7b6412fe97bbf74a5b024f3985a517ec43f49/images/pybd_mic.jpg -------------------------------------------------------------------------------- /images/pybd_uda.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miketeachman/micropython-i2s-examples/baf7b6412fe97bbf74a5b024f3985a517ec43f49/images/pybd_uda.jpg -------------------------------------------------------------------------------- /images/pybv1_mic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miketeachman/micropython-i2s-examples/baf7b6412fe97bbf74a5b024f3985a517ec43f49/images/pybv1_mic.jpg -------------------------------------------------------------------------------- /images/pybv1_uda_22awg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miketeachman/micropython-i2s-examples/baf7b6412fe97bbf74a5b024f3985a517ec43f49/images/pybv1_uda_22awg.jpg -------------------------------------------------------------------------------- /images/pybv1_uda_jumpers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miketeachman/micropython-i2s-examples/baf7b6412fe97bbf74a5b024f3985a517ec43f49/images/pybv1_uda_jumpers.jpg -------------------------------------------------------------------------------- /images/solid_wire_22awg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miketeachman/micropython-i2s-examples/baf7b6412fe97bbf74a5b024f3985a517ec43f49/images/solid_wire_22awg.jpg -------------------------------------------------------------------------------- /teensy_audio_shield/play_teensy_audio_shield.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # Copyright (c) 2022 Mike Teachman 3 | # https://opensource.org/licenses/MIT 4 | 5 | # Purpose: Play a WAV audio file using the Teensy Audio Shield, Rev D 6 | # 7 | # - read audio samples from a WAV file on SD Card 8 | # - write audio samples to a SGTL5000 codec on the Teensy Audio Shield 9 | # - the WAV file will play continuously in a loop until 10 | # a keyboard interrupt is detected or the board is reset 11 | # 12 | # blocking version 13 | # - the write() method blocks until the entire sample buffer is written to the I2S interface 14 | # 15 | # requires a MicroPython driver for the SGTL5000 codec 16 | 17 | import os 18 | from machine import I2C 19 | from machine import I2S 20 | from machine import Pin 21 | from machine import SDCard 22 | from sgtl5000 import CODEC 23 | 24 | sd = SDCard(1) # Teensy 4.1: sck=45, mosi=43, miso=42, cs=44 25 | os.mount(sd, "/sd") 26 | 27 | # ======= I2S CONFIGURATION ======= 28 | SCK_PIN = 21 29 | WS_PIN = 20 30 | SD_PIN = 7 31 | MCK_PIN = 23 32 | I2S_ID = 1 33 | BUFFER_LENGTH_IN_BYTES = 40000 34 | # ======= I2S CONFIGURATION ======= 35 | 36 | # ======= AUDIO CONFIGURATION ======= 37 | WAV_FILE = "le-blues-de-la-vache-44k1-16bits-stereo.wav" 38 | WAV_SAMPLE_SIZE_IN_BITS = 16 39 | FORMAT = I2S.STEREO 40 | SAMPLE_RATE_IN_HZ = 44100 41 | # ======= AUDIO CONFIGURATION ======= 42 | 43 | audio_out = I2S( 44 | I2S_ID, 45 | sck=Pin(SCK_PIN), 46 | ws=Pin(WS_PIN), 47 | sd=Pin(SD_PIN), 48 | mck=Pin(MCK_PIN), 49 | mode=I2S.TX, 50 | bits=WAV_SAMPLE_SIZE_IN_BITS, 51 | format=FORMAT, 52 | rate=SAMPLE_RATE_IN_HZ, 53 | ibuf=BUFFER_LENGTH_IN_BYTES, 54 | ) 55 | 56 | # configure the SGTL5000 codec 57 | i2c = I2C(0, freq=400000) 58 | codec = CODEC(0x0A, i2c) 59 | codec.mute_dac(False) 60 | codec.dac_volume(0.9, 0.9) 61 | codec.headphone_select(0) 62 | codec.mute_headphone(False) 63 | codec.volume(0.7, 0.7) 64 | 65 | wav = open("/sd/{}".format(WAV_FILE), "rb") 66 | _ = wav.seek(44) # advance to first byte of Data section in WAV file 67 | 68 | # allocate sample array 69 | # memoryview used to reduce heap allocation 70 | wav_samples = bytearray(10000) 71 | wav_samples_mv = memoryview(wav_samples) 72 | 73 | # continuously read audio samples from the WAV file 74 | # and write them to an I2S DAC 75 | print("========== START PLAYBACK ==========") 76 | try: 77 | while True: 78 | num_read = wav.readinto(wav_samples_mv) 79 | # end of WAV file? 80 | if num_read == 0: 81 | # end-of-file, advance to first byte of Data section 82 | _ = wav.seek(44) 83 | else: 84 | _ = audio_out.write(wav_samples_mv[:num_read]) 85 | except (KeyboardInterrupt, Exception) as e: 86 | print("caught exception {} {}".format(type(e).__name__, e)) 87 | 88 | # cleanup 89 | wav.close() 90 | os.umount("/sd") 91 | sd.deinit() 92 | audio_out.deinit() 93 | print("Done") 94 | -------------------------------------------------------------------------------- /teensy_audio_shield/play_teensy_audio_shield_esp32.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # Copyright (c) 2023 Mike Teachman 3 | # https://opensource.org/licenses/MIT 4 | 5 | # Purpose: Play a WAV audio file using the Teensy Audio Shield, Rev D 6 | # 7 | # - read audio samples from a WAV file on SD Card 8 | # - master clock is generated using the machine.PWM class 9 | # - write audio samples to a SGTL5000 codec on the Teensy Audio Shield 10 | # - the WAV file will play continuously in a loop until 11 | # a keyboard interrupt is detected or the board is reset 12 | # 13 | # blocking version 14 | # - the write() method blocks until the entire sample buffer is written to the I2S interface 15 | # 16 | # Requires a MicroPython driver for the SGTL5000 codec. Copy sgtl5000.py to the file system. 17 | # https://github.com/rdagger/micropython-sgtl5000 18 | # 19 | # This example code was tested on a Lolin D32 Pro ESP32 board, but can 20 | # be run on other boards with minor changes to SD card, I2C, and I2S pin assignments. 21 | # https://www.wemos.cc/en/latest/d32/d32_pro.html 22 | 23 | import os 24 | import time 25 | from machine import PWM 26 | from machine import I2C 27 | from machine import I2S 28 | from machine import Pin 29 | from machine import SDCard 30 | from sgtl5000 import CODEC 31 | 32 | sd = SDCard(slot=2, cs=Pin(4)) # sck=18, mosi=23, miso=19, cs=4 33 | os.mount(sd, "/sd") 34 | 35 | # ======= I2S CONFIGURATION ======= 36 | SCK_PIN = 33 37 | WS_PIN = 25 38 | SD_PIN = 32 39 | MCK_PIN = 5 40 | I2S_ID = 1 41 | BUFFER_LENGTH_IN_BYTES = 40000 42 | # ======= I2S CONFIGURATION ======= 43 | 44 | # ======= AUDIO CONFIGURATION ======= 45 | WAV_FILE = "le-blues-de-la-vache-44k1-16bits-stereo.wav" 46 | WAV_SAMPLE_SIZE_IN_BITS = 16 47 | FORMAT = I2S.STEREO 48 | SAMPLE_RATE_IN_HZ = 44100 49 | # ======= AUDIO CONFIGURATION ======= 50 | 51 | audio_out = I2S( 52 | I2S_ID, 53 | sck=Pin(SCK_PIN), 54 | ws=Pin(WS_PIN), 55 | sd=Pin(SD_PIN), 56 | mode=I2S.TX, 57 | bits=WAV_SAMPLE_SIZE_IN_BITS, 58 | format=FORMAT, 59 | rate=SAMPLE_RATE_IN_HZ, 60 | ibuf=BUFFER_LENGTH_IN_BYTES, 61 | ) 62 | 63 | # start the master clock, 50% duty cycle 64 | # important note: the SGTL5000 device must have a master clock *BEFORE* I2C will work 65 | pwm = PWM(Pin(MCK_PIN), freq=SAMPLE_RATE_IN_HZ*256, duty_u16=32768) 66 | 67 | # configure the SGTL5000 codec 68 | i2c = I2C(0, sda=Pin(26), scl=Pin(27), freq=400000) 69 | codec = CODEC(0x0A, i2c) 70 | codec.mute_dac(False) 71 | codec.dac_volume(0.9, 0.9) 72 | codec.headphone_select(0) 73 | codec.mute_headphone(False) 74 | codec.volume(0.7, 0.7) 75 | 76 | wav = open("/sd/{}".format(WAV_FILE), "rb") 77 | _ = wav.seek(44) # advance to first byte of Data section in WAV file 78 | 79 | # allocate sample array 80 | # memoryview used to reduce heap allocation 81 | wav_samples = bytearray(10000) 82 | wav_samples_mv = memoryview(wav_samples) 83 | 84 | # continuously read audio samples from the WAV file 85 | # and write them to an I2S DAC 86 | print("========== START PLAYBACK ==========") 87 | try: 88 | while True: 89 | num_read = wav.readinto(wav_samples_mv) 90 | # end of WAV file? 91 | if num_read == 0: 92 | # end-of-file, advance to first byte of Data section 93 | _ = wav.seek(44) 94 | else: 95 | _ = audio_out.write(wav_samples_mv[:num_read]) 96 | except (KeyboardInterrupt, Exception) as e: 97 | print("caught exception {} {}".format(type(e).__name__, e)) 98 | 99 | # cleanup 100 | wav.close() 101 | os.umount("/sd") 102 | sd.deinit() 103 | audio_out.deinit() 104 | print("Done") 105 | -------------------------------------------------------------------------------- /teensy_audio_shield/record_teensy_audio_shield.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # Copyright (c) 2022 Mike Teachman 3 | # https://opensource.org/licenses/MIT 4 | 5 | # Purpose: Read audio samples to a SD card using the Teensy Audio Shield, Rev D 6 | # 7 | # - read 32-bit audio samples from I2S hardware, typically an I2S MEMS Microphone 8 | # - convert 32-bit samples to specified bit size 9 | # - write samples to a SD card file in WAV format 10 | # - samples will be continuously written to the WAV file 11 | # until a keyboard interrupt (ctrl-c) is detected 12 | # 13 | # Blocking version 14 | # - the readinto() method blocks until 15 | # the supplied buffer is filled 16 | # 17 | # requires a MicroPython driver for the SGTL5000 codec 18 | 19 | import os 20 | import time 21 | from machine import I2C 22 | from machine import I2S 23 | from machine import Pin 24 | from sgtl5000 import CODEC 25 | from machine import SDCard 26 | 27 | sd = SDCard(1) # Teensy 4.1: sck=45, mosi=43, miso=42, cs=44 28 | os.mount(sd, "/sd") 29 | 30 | # ======= I2S CONFIGURATION ======= 31 | SCK_PIN = 21 32 | WS_PIN = 20 33 | SD_PIN = 8 34 | MCK_PIN = 23 35 | I2S_ID = 1 36 | BUFFER_LENGTH_IN_BYTES = 100000 37 | # ======= I2S CONFIGURATION ======= 38 | 39 | # ======= AUDIO CONFIGURATION ======= 40 | WAV_FILE = "mic.wav" 41 | RECORD_TIME_IN_SECONDS = 10 42 | WAV_SAMPLE_SIZE_IN_BITS = 16 43 | FORMAT = I2S.MONO 44 | SAMPLE_RATE_IN_HZ = 44100 45 | # ======= AUDIO CONFIGURATION ======= 46 | 47 | format_to_channels = {I2S.MONO: 1, I2S.STEREO: 2} 48 | NUM_CHANNELS = format_to_channels[FORMAT] 49 | WAV_SAMPLE_SIZE_IN_BYTES = WAV_SAMPLE_SIZE_IN_BITS // 8 50 | RECORDING_SIZE_IN_BYTES = ( 51 | RECORD_TIME_IN_SECONDS * SAMPLE_RATE_IN_HZ * WAV_SAMPLE_SIZE_IN_BYTES * NUM_CHANNELS 52 | ) 53 | 54 | def create_wav_header(sampleRate, bitsPerSample, num_channels, num_samples): 55 | datasize = num_samples * num_channels * bitsPerSample // 8 56 | o = bytes("RIFF", "ascii") # (4byte) Marks file as RIFF 57 | o += (datasize + 36).to_bytes( 58 | 4, "little" 59 | ) # (4byte) File size in bytes excluding this and RIFF marker 60 | o += bytes("WAVE", "ascii") # (4byte) File type 61 | o += bytes("fmt ", "ascii") # (4byte) Format Chunk Marker 62 | o += (16).to_bytes(4, "little") # (4byte) Length of above format data 63 | o += (1).to_bytes(2, "little") # (2byte) Format type (1 - PCM) 64 | o += (num_channels).to_bytes(2, "little") # (2byte) 65 | o += (sampleRate).to_bytes(4, "little") # (4byte) 66 | o += (sampleRate * num_channels * bitsPerSample // 8).to_bytes(4, "little") # (4byte) 67 | o += (num_channels * bitsPerSample // 8).to_bytes(2, "little") # (2byte) 68 | o += (bitsPerSample).to_bytes(2, "little") # (2byte) 69 | o += bytes("data", "ascii") # (4byte) Data Chunk Marker 70 | o += (datasize).to_bytes(4, "little") # (4byte) Data size in bytes 71 | return o 72 | 73 | 74 | wav = open("/sd/{}".format(WAV_FILE), "wb") 75 | 76 | # create header for WAV file and write to SD card 77 | wav_header = create_wav_header( 78 | SAMPLE_RATE_IN_HZ, 79 | WAV_SAMPLE_SIZE_IN_BITS, 80 | NUM_CHANNELS, 81 | SAMPLE_RATE_IN_HZ * RECORD_TIME_IN_SECONDS, 82 | ) 83 | num_bytes_written = wav.write(wav_header) 84 | 85 | audio_in = I2S( 86 | I2S_ID, 87 | sck=Pin(SCK_PIN), 88 | ws=Pin(WS_PIN), 89 | sd=Pin(SD_PIN), 90 | mck=Pin(MCK_PIN), 91 | mode=I2S.RX, 92 | bits=WAV_SAMPLE_SIZE_IN_BITS, 93 | format=FORMAT, 94 | rate=SAMPLE_RATE_IN_HZ, 95 | ibuf=BUFFER_LENGTH_IN_BYTES, 96 | ) 97 | 98 | # configure the SGTL5000 codec 99 | i2c = I2C(0, freq=400000) 100 | codec = CODEC(0x0A, i2c) 101 | codec.mute_dac(True) 102 | codec.mute_headphone(True) 103 | codec.input_select(1) 104 | codec.mic_gain(20) 105 | 106 | # allocate sample arrays 107 | # memoryview used to reduce heap allocation in while loop 108 | mic_samples = bytearray(10000) 109 | mic_samples_mv = memoryview(mic_samples) 110 | 111 | num_sample_bytes_written_to_wav = 0 112 | 113 | print("Recording size: {} bytes".format(RECORDING_SIZE_IN_BYTES)) 114 | print("========== START RECORDING ==========") 115 | try: 116 | while num_sample_bytes_written_to_wav < RECORDING_SIZE_IN_BYTES: 117 | # read a block of samples from the I2S microphone 118 | num_bytes_read_from_mic = audio_in.readinto(mic_samples_mv) 119 | if num_bytes_read_from_mic > 0: 120 | num_bytes_to_write = min( 121 | num_bytes_read_from_mic, RECORDING_SIZE_IN_BYTES - num_sample_bytes_written_to_wav 122 | ) 123 | # write samples to WAV file 124 | num_bytes_written = wav.write(mic_samples_mv[:num_bytes_to_write]) 125 | num_sample_bytes_written_to_wav += num_bytes_written 126 | 127 | print("========== DONE RECORDING ==========") 128 | except (KeyboardInterrupt, Exception) as e: 129 | print("caught exception {} {}".format(type(e).__name__, e)) 130 | 131 | # cleanup 132 | wav.close() 133 | os.umount("/sd") 134 | sd.deinit() 135 | audio_in.deinit() 136 | print("Done") 137 | -------------------------------------------------------------------------------- /wav/le-blues-de-la-vache-44k1-16bits-stereo.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miketeachman/micropython-i2s-examples/baf7b6412fe97bbf74a5b024f3985a517ec43f49/wav/le-blues-de-la-vache-44k1-16bits-stereo.wav -------------------------------------------------------------------------------- /wav/music-16k-16bits-mono.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miketeachman/micropython-i2s-examples/baf7b6412fe97bbf74a5b024f3985a517ec43f49/wav/music-16k-16bits-mono.wav -------------------------------------------------------------------------------- /wav/music-16k-16bits-stereo.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miketeachman/micropython-i2s-examples/baf7b6412fe97bbf74a5b024f3985a517ec43f49/wav/music-16k-16bits-stereo.wav -------------------------------------------------------------------------------- /wav/music-16k-32bits-mono.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miketeachman/micropython-i2s-examples/baf7b6412fe97bbf74a5b024f3985a517ec43f49/wav/music-16k-32bits-mono.wav -------------------------------------------------------------------------------- /wav/music-16k-32bits-stereo.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miketeachman/micropython-i2s-examples/baf7b6412fe97bbf74a5b024f3985a517ec43f49/wav/music-16k-32bits-stereo.wav -------------------------------------------------------------------------------- /wav/side-to-side-8k-16bits-stereo.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miketeachman/micropython-i2s-examples/baf7b6412fe97bbf74a5b024f3985a517ec43f49/wav/side-to-side-8k-16bits-stereo.wav --------------------------------------------------------------------------------