Output I2S audio with PIO/DMA

Requires a MAX98357A amplifier and a 2W 8ohms speaker
This commit is contained in:
Vincent Mistler
2022-12-02 17:24:39 +00:00
parent a9f54dc37d
commit 29a04ad832
8 changed files with 285 additions and 31 deletions
+100
View File
@@ -0,0 +1,100 @@
/**
* MIT License
*
* Copyright (c) 2022 Vincent Mistler
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "i2s.h"
i2s_config_t i2s_get_default_config(void) {
i2s_config_t i2s_config = {
.sample_freq = 44100,
.channel_count = 2,
.data_pin = 26,
.clock_pin_base = 27,
.pio = pio0,
.sm = 0,
.dma_channel = 0,
.dma_buf = NULL,
.dma_trans_count = 0,
};
return i2s_config;
}
void i2s_init(i2s_config_t *i2s_config) {
uint8_t func=GPIO_FUNC_PIO0; // TODO: GPIO_FUNC_PIO0 for pio0 or GPIO_FUNC_PIO1 for pio1
gpio_set_function(i2s_config->data_pin, GPIO_FUNC_PIO0);
gpio_set_function(i2s_config->clock_pin_base, GPIO_FUNC_PIO0);
gpio_set_function(i2s_config->clock_pin_base+1, GPIO_FUNC_PIO0);
i2s_config->sm = pio_claim_unused_sm(i2s_config->pio, true);
uint offset = pio_add_program(i2s_config->pio, &audio_i2s_program);
audio_i2s_program_init(i2s_config->pio, i2s_config->sm , offset, i2s_config->data_pin , i2s_config->clock_pin_base);
// Set PIO clock
uint32_t system_clock_frequency = clock_get_hz(clk_sys);
uint32_t divider = system_clock_frequency * 4 / i2s_config->sample_freq; // avoid arithmetic overflow
pio_sm_set_clkdiv_int_frac(i2s_config->pio, i2s_config->sm , divider >> 8u, divider & 0xffu);
pio_sm_set_enabled(i2s_config->pio, i2s_config->sm, false);
// Allocate memory for the DMA buffer
i2s_config->dma_buf=malloc(i2s_config->dma_trans_count*sizeof(uint32_t));
// Direct Memory Access setup
i2s_config->dma_channel = dma_claim_unused_channel(true);
dma_channel_config dma_config = dma_channel_get_default_config(i2s_config->dma_channel);
channel_config_set_read_increment(&dma_config, true);
channel_config_set_write_increment(&dma_config, false);
channel_config_set_dreq(&dma_config, pio_get_dreq(i2s_config->pio, i2s_config->sm, true));
channel_config_set_transfer_data_size(&dma_config, DMA_SIZE_32);
dma_channel_configure(i2s_config->dma_channel,
&dma_config,
&(i2s_config->pio->txf[i2s_config->sm]), // Destination pointer
i2s_config->dma_buf, // Source pointer
i2s_config->dma_trans_count, // Number of 32 bits words to transfer
false // Start immediately
);
pio_sm_set_enabled(i2s_config->pio, i2s_config->sm , true);
}
void i2s_write(const i2s_config_t *i2s_config,const int16_t *samples,const size_t len) {
for(size_t i=0;i<len;i++) {
pio_sm_put_blocking(i2s_config->pio, i2s_config->sm, (uint32_t)samples[i]);
}
}
// Write samples to DMA buffer and initiate DMA transfer (non blocking)
void i2s_dma_write(i2s_config_t *i2s_config,const int16_t *samples) {
// Wait the completion of the previous DMA transfer
dma_channel_wait_for_finish_blocking(i2s_config->dma_channel);
// Copy samples into the DMA buffer
memcpy(i2s_config->dma_buf,samples,i2s_config->dma_trans_count*sizeof(int32_t));
// Initiate the DMA transfer
dma_channel_transfer_from_buffer_now(i2s_config->dma_channel,
i2s_config->dma_buf,
i2s_config->dma_trans_count);
}