STM32 Music Player
Creating an audio player using a STM32 MCU is relatively straight forward, but does include some interesting steps. Audio out could in principle be created using one of the built-in DACs but for better audio quality a proper audio stereo DAC should be used. Fortunately they are readily available and not expensive. Research into audio play on STM32.
Videos
Teaser:
First video in the series:
Second video in the series:
I2S Audio DAC
The I2S part is relatively simple. It essentially consists of 3 functions:
void process_buffer(int16_t *target_buffer) { int16_t buf[2 * I2S_DMA_BUFFER_SAMPLES] = { 0 }; unsigned int bytes_read = 0; if (f_read(&music_file, &buf, sizeof(buf), &bytes_read) == FR_OK) { for (int i = 0; i < 2 * I2S_DMA_BUFFER_SAMPLES; ++i) { buf[i] = buf[i] * amplifier; } memcpy(target_buffer, buf, sizeof(buf)); if (bytes_read < sizeof(buf)) { printf("File done!\n"); set_i2s_freq(0); open_next_file = 1; } } } void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *hi2s) { do_buffer = &i2s_dma_buffer[2 * I2S_DMA_BUFFER_SAMPLES]; // Second half } void HAL_I2S_TxHalfCpltCallback(I2S_HandleTypeDef *hi2s) { do_buffer = &i2s_dma_buffer[0]; // First half }
The DMA callbacks just set a variable to the address of the buffer to fill. In the main loop, this will trigger a call to process_buffer with that address. The process_buffer read a chunk of data from the already open file, multiply each value with a multiplier (volume adjustment) and copy those values into the buffer.
The important part is that the sample rate of the I2S is set to the sample rate of the file. This is done after parsing the file header by calling:
void set_i2s_freq(uint32_t freq) { printf("Setting I2S sample frequency to: %lu\n", freq); // Stop the DMA transfers HAL_I2S_DMAStop(&hi2s2); // Deinit HAL_I2S_DeInit(&hi2s2); if (freq > 0) { hi2s2.Init.AudioFreq = freq; HAL_I2S_Init(&hi2s2); HAL_I2S_Transmit_DMA(&hi2s2, (uint16_t*) &i2s_dma_buffer, I2S_DMA_BUFFER_SIZE); } }
This function will stop the I2S DMA and I2S itself, configure I2S with a new sample rate (AudioFreq) and reinitialize both I2S and DMA. If the freq is set to 0 - I2S will not be restarted.
Parsing WAV files
A typical WAV file consist of a header followed by audio data. The header specifies the audio format.
On Wikipedia, the WAV format is described like this:
[Master RIFF chunk] FileTypeBlocID (4 bytes) : Identifier « RIFF » (0x52, 0x49, 0x46, 0x46) FileSize (4 bytes) : Overall file size minus 8 bytes FileFormatID (4 bytes) : Format = « WAVE » (0x57, 0x41, 0x56, 0x45) [Chunk describing the data format] FormatBlocID (4 bytes) : Identifier « fmt␣ » (0x66, 0x6D, 0x74, 0x20) BlocSize (4 bytes) : Chunk size minus 8 bytes, which is 16 bytes here (0x10) AudioFormat (2 bytes) : Audio format (1: PCM integer, 3: IEEE 754 float) NbrChannels (2 bytes) : Number of channels Frequency (4 bytes) : Sample rate (in hertz) BytePerSec (4 bytes) : Number of bytes to read per second (Frequency * BytePerBloc). BytePerBloc (2 bytes) : Number of bytes per block (NbrChannels * BitsPerSample / 8). BitsPerSample (2 bytes) : Number of bits per sample [Chunk containing the sampled data] DataBlocID (4 bytes) : Identifier « data » (0x64, 0x61, 0x74, 0x61) DataSize (4 bytes) : SampledData size SampledData
Looking an example header, it may look like this:
00000000 52 49 46 46 f4 82 5f 03 57 41 56 45 66 6d 74 20 |RIFF.._.WAVEfmt | 00000010 10 00 00 00 01 00 02 00 80 bb 00 00 00 ee 02 00 |................| 00000020 04 00 10 00 4c 49 53 54 c8 00 00 00 49 4e 46 4f |....LIST....INFO| 00000030 49 41 52 54 0d 00 00 00 46 65 6c 69 70 65 20 53 |IART....Felipe S| 00000040 61 72 72 6f 00 00 49 43 52 44 05 00 00 00 32 30 |arro..ICRD....20| 00000050 30 38 00 00 49 47 4e 52 0a 00 00 00 43 6c 61 73 |08..IGNR....Clas| 00000060 73 69 63 61 6c 00 49 4e 41 4d 3d 00 00 00 42 61 |sical.INAM=...Ba| 00000070 63 68 20 2d 20 53 69 6c 6f 74 74 69 20 2d 20 22 |ch - Silotti - "| 00000080 41 69 72 22 20 20 66 72 6f 6d 20 4f 72 63 68 65 |Air" from Orche| 00000090 73 74 72 61 20 53 75 69 74 65 20 4e 6f 2e 20 33 |stra Suite No. 3| 000000a0 2c 20 42 57 56 20 31 30 36 38 00 00 49 50 52 44 |, BWV 1068..IPRD| 000000b0 2a 00 00 00 68 74 74 70 3a 2f 2f 77 77 77 2e 66 |*...http://www.f| 000000c0 65 6c 69 70 65 73 61 72 72 6f 2e 70 61 67 69 6e |elipesarro.pagin| 000000d0 61 2d 6f 66 69 63 69 61 6c 2e 63 6f 6d 00 49 53 |a-oficial.com.IS| 000000e0 46 54 0d 00 00 00 4c 61 76 66 36 31 2e 37 2e 31 |FT....Lavf61.7.1| 000000f0 30 30 00 00 64 61 74 61 00 82 5f 03 00 00 00 00 |00..data.._.....| 00000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
Let's see if we can make sense of this. The format begins with RIFF, followed by 4 byte and WAVE - 12 bytes in all. It is immediately followed by "fmt " (notice the space).