pico-extras/src/rp2_common/pico_audio_i2s/audio_i2s.c

363 wiersze
13 KiB
C

/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include "pico/audio_i2s.h"
#include "audio_i2s.pio.h"
#include "hardware/pio.h"
#include "hardware/gpio.h"
#include "hardware/dma.h"
#include "hardware/irq.h"
#include "hardware/clocks.h"
CU_REGISTER_DEBUG_PINS(audio_timing)
// ---- select at most one ---
//CU_SELECT_DEBUG_PINS(audio_timing)
#if PICO_AUDIO_I2S_MONO_OUTPUT
#define i2s_dma_configure_size DMA_SIZE_16
#else
#define i2s_dma_configure_size DMA_SIZE_32
#endif
#define audio_pio __CONCAT(pio, PICO_AUDIO_I2S_PIO)
#define GPIO_FUNC_PIOx __CONCAT(GPIO_FUNC_PIO, PICO_AUDIO_I2S_PIO)
#define DREQ_PIOx_TX0 __CONCAT(__CONCAT(DREQ_PIO, PICO_AUDIO_I2S_PIO), _TX0)
#define dma_intsx __CONCAT(dma_hw->ints, PICO_AUDIO_I2S_DMA_IRQ)
#define dma_channel_set_irqx_enabled __CONCAT(__CONCAT(dma_channel_set_irq, PICO_AUDIO_I2S_DMA_IRQ),_enabled)
#define DMA_IRQ_x __CONCAT(DMA_IRQ_, PICO_AUDIO_I2S_DMA_IRQ)
struct {
audio_buffer_t *playing_buffer;
uint32_t freq;
uint8_t pio_sm;
uint8_t dma_channel;
} shared_state;
audio_format_t pio_i2s_consumer_format;
audio_buffer_format_t pio_i2s_consumer_buffer_format = {
.format = &pio_i2s_consumer_format,
};
static audio_buffer_t silence_buffer;
static void __isr __time_critical_func(audio_i2s_dma_irq_handler)();
const audio_format_t *audio_i2s_setup(const audio_format_t *intended_audio_format,
const audio_i2s_config_t *config) {
uint func = GPIO_FUNC_PIOx;
gpio_set_function(config->data_pin, func);
gpio_set_function(config->clock_pin_base, func);
gpio_set_function(config->clock_pin_base + 1, func);
uint8_t sm = shared_state.pio_sm = config->pio_sm;
pio_sm_claim(audio_pio, sm);
uint offset = pio_add_program(audio_pio, &audio_i2s_program);
audio_i2s_program_init(audio_pio, sm, offset, config->data_pin, config->clock_pin_base);
silence_buffer.buffer = pico_buffer_alloc(PICO_AUDIO_I2S_BUFFER_SAMPLE_LENGTH * 4);
silence_buffer.sample_count = PICO_AUDIO_I2S_BUFFER_SAMPLE_LENGTH;
silence_buffer.format = &pio_i2s_consumer_buffer_format;
__mem_fence_release();
uint8_t dma_channel = config->dma_channel;
dma_channel_claim(dma_channel);
shared_state.dma_channel = dma_channel;
dma_channel_config dma_config = dma_channel_get_default_config(dma_channel);
channel_config_set_dreq(&dma_config,
DREQ_PIOx_TX0 + sm
);
channel_config_set_transfer_data_size(&dma_config, i2s_dma_configure_size);
dma_channel_configure(dma_channel,
&dma_config,
&audio_pio->txf[sm], // dest
NULL, // src
0, // count
false // trigger
);
irq_add_shared_handler(DMA_IRQ_x, audio_i2s_dma_irq_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
dma_channel_set_irqx_enabled(dma_channel, 1);
return intended_audio_format;
}
static audio_buffer_pool_t *audio_i2s_consumer;
static void update_pio_frequency(uint32_t sample_freq) {
printf("setting pio freq %d\n", (int) sample_freq);
uint32_t system_clock_frequency = clock_get_hz(clk_sys);
assert(system_clock_frequency < 0x40000000);
uint32_t divider = system_clock_frequency * 4 / sample_freq; // avoid arithmetic overflow
printf("System clock at %u, I2S clock divider 0x%x/256\n", (uint) system_clock_frequency, (uint)divider);
assert(divider < 0x1000000);
pio_sm_set_clkdiv_int_frac(audio_pio, shared_state.pio_sm, divider >> 8u, divider & 0xffu);
shared_state.freq = sample_freq;
}
static audio_buffer_t *wrap_consumer_take(audio_connection_t *connection, bool block) {
// support dynamic frequency shifting
if (connection->producer_pool->format->sample_freq != shared_state.freq) {
update_pio_frequency(connection->producer_pool->format->sample_freq);
}
#if PICO_AUDIO_I2S_MONO_INPUT
#if PICO_AUDIO_I2S_MONO_OUTPUT
return mono_to_mono_consumer_take(connection, block);
#else
return mono_to_stereo_consumer_take(connection, block);
#endif
#else
#if PICO_AUDIO_I2S_MONO_OUTPUT
unsupported;
#else
return stereo_to_stereo_consumer_take(connection, block);
#endif
#endif
}
static void wrap_producer_give(audio_connection_t *connection, audio_buffer_t *buffer) {
// support dynamic frequency shifting
if (connection->producer_pool->format->sample_freq != shared_state.freq) {
update_pio_frequency(connection->producer_pool->format->sample_freq);
}
#if PICO_AUDIO_I2S_MONO_INPUT
#if PICO_AUDIO_I2S_MONO_OUTPUT
assert(false);
// return mono_to_mono_producer_give(connection, block);
#else
assert(false);
//return mono_to_stereo_producer_give(connection, buffer);
#endif
#else
#if PICO_AUDIO_I2S_MONO_OUTPUT
unsupported;
#else
return stereo_to_stereo_producer_give(connection, buffer);
#endif
#endif
}
static struct buffer_copying_on_consumer_take_connection m2s_audio_i2s_ct_connection = {
.core = {
.consumer_pool_take = wrap_consumer_take,
.consumer_pool_give = consumer_pool_give_buffer_default,
.producer_pool_take = producer_pool_take_buffer_default,
.producer_pool_give = producer_pool_give_buffer_default,
}
};
static struct producer_pool_blocking_give_connection m2s_audio_i2s_pg_connection = {
.core = {
.consumer_pool_take = consumer_pool_take_buffer_default,
.consumer_pool_give = consumer_pool_give_buffer_default,
.producer_pool_take = producer_pool_take_buffer_default,
.producer_pool_give = wrap_producer_give,
}
};
bool audio_i2s_connect_thru(audio_buffer_pool_t *producer, audio_connection_t *connection) {
return audio_i2s_connect_extra(producer, false, 2, 256, connection);
}
bool audio_i2s_connect(audio_buffer_pool_t *producer) {
return audio_i2s_connect_thru(producer, NULL);
}
bool audio_i2s_connect_extra(audio_buffer_pool_t *producer, bool buffer_on_give, uint buffer_count,
uint samples_per_buffer, audio_connection_t *connection) {
printf("Connecting PIO I2S audio\n");
// todo we need to pick a connection based on the frequency - e.g. 22050 can be more simply upsampled to 44100
assert(producer->format->format == AUDIO_BUFFER_FORMAT_PCM_S16);
pio_i2s_consumer_format.format = AUDIO_BUFFER_FORMAT_PCM_S16;
// todo we could do mono
// todo we can't match exact, so we should return what we can do
pio_i2s_consumer_format.sample_freq = producer->format->sample_freq;
#if PICO_AUDIO_I2S_MONO_OUTPUT
pio_i2s_consumer_format.channel_count = 1;
pio_i2s_consumer_buffer_format.sample_stride = 2;
#else
pio_i2s_consumer_format.channel_count = 2;
pio_i2s_consumer_buffer_format.sample_stride = 4;
#endif
audio_i2s_consumer = audio_new_consumer_pool(&pio_i2s_consumer_buffer_format, buffer_count, samples_per_buffer);
update_pio_frequency(producer->format->sample_freq);
// todo cleanup threading
__mem_fence_release();
if (!connection) {
if (producer->format->channel_count == 2) {
#if PICO_AUDIO_I2S_MONO_INPUT
panic("need to merge channels down\n");
#else
#if PICO_AUDIO_I2S_MONO_OUTPUT
panic("trying to play stereo thru mono not yet supported");
#else
printf("Copying stereo to stereo at %d Hz\n", (int) producer->format->sample_freq);
#endif
#endif
// todo we should support pass thru option anyway
printf("TODO... not completing stereo audio connection properly!\n");
} else {
#if PICO_AUDIO_I2S_MONO_OUTPUT
printf("Copying mono to mono at %d Hz\n", (int) producer->format->sample_freq);
#else
printf("Converting mono to stereo at %d Hz\n", (int) producer->format->sample_freq);
#endif
}
connection = buffer_on_give ? &m2s_audio_i2s_pg_connection.core : &m2s_audio_i2s_ct_connection.core;
}
audio_complete_connection(connection, producer, audio_i2s_consumer);
return true;
}
static struct buffer_copying_on_consumer_take_connection m2s_audio_i2s_connection_s8 = {
.core = {
#if PICO_AUDIO_I2S_MONO_OUTPUT
.consumer_pool_take = mono_s8_to_mono_consumer_take,
#else
.consumer_pool_take = mono_s8_to_stereo_consumer_take,
#endif
.consumer_pool_give = consumer_pool_give_buffer_default,
.producer_pool_take = producer_pool_take_buffer_default,
.producer_pool_give = producer_pool_give_buffer_default,
}
};
bool audio_i2s_connect_s8(audio_buffer_pool_t *producer) {
printf("Connecting PIO I2S audio (U8)\n");
// todo we need to pick a connection based on the frequency - e.g. 22050 can be more simply upsampled to 44100
assert(producer->format->format == AUDIO_BUFFER_FORMAT_PCM_S8);
pio_i2s_consumer_format.format = AUDIO_BUFFER_FORMAT_PCM_S16;
// todo we could do mono
// todo we can't match exact, so we should return what we can do
pio_i2s_consumer_format.sample_freq = producer->format->sample_freq;
#if PICO_AUDIO_I2S_MONO_OUTPUT
pio_i2s_consumer_format.channel_count = 1;
pio_i2s_consumer_buffer_format.sample_stride = 2;
#else
pio_i2s_consumer_format.channel_count = 2;
pio_i2s_consumer_buffer_format.sample_stride = 4;
#endif
// we do this on take so should do it quickly...
uint samples_per_buffer = 256;
// todo with take we really only need 1 buffer
audio_i2s_consumer = audio_new_consumer_pool(&pio_i2s_consumer_buffer_format, 2, samples_per_buffer);
// todo we need a method to calculate this in clocks
uint32_t system_clock_frequency = 48000000;
// uint32_t divider = system_clock_frequency * 256 / producer->format->sample_freq * 16 * 4;
uint32_t divider = system_clock_frequency * 4 / producer->format->sample_freq; // avoid arithmetic overflow
pio_sm_set_clkdiv_int_frac(audio_pio, shared_state.pio_sm, divider >> 8u, divider & 0xffu);
// todo cleanup threading
__mem_fence_release();
audio_connection_t *connection;
if (producer->format->channel_count == 2) {
#if PICO_AUDIO_I2S_MONO_OUTPUT
panic("trying to play stereo thru mono not yet supported");
#endif
// todo we should support pass thru option anyway
printf("TODO... not completing stereo audio connection properly!\n");
connection = &m2s_audio_i2s_connection_s8.core;
} else {
#if PICO_AUDIO_I2S_MONO_OUTPUT
printf("Copying mono to mono at %d Hz\n", (int) producer->format->sample_freq);
#else
printf("Converting mono to stereo at %d Hz\n", (int) producer->format->sample_freq);
#endif
connection = &m2s_audio_i2s_connection_s8.core;
}
audio_complete_connection(connection, producer, audio_i2s_consumer);
return true;
}
static inline void audio_start_dma_transfer() {
assert(!shared_state.playing_buffer);
audio_buffer_t *ab = take_audio_buffer(audio_i2s_consumer, false);
shared_state.playing_buffer = ab;
if (!ab) {
DEBUG_PINS_XOR(audio_timing, 1);
DEBUG_PINS_XOR(audio_timing, 2);
DEBUG_PINS_XOR(audio_timing, 1);
//DEBUG_PINS_XOR(audio_timing, 2);
// just play some silence
ab = &silence_buffer;
}
assert(ab->sample_count);
// todo better naming of format->format->format!!
assert(ab->format->format->format == AUDIO_BUFFER_FORMAT_PCM_S16);
#if PICO_AUDIO_I2S_MONO_OUTPUT
assert(ab->format->format->channel_count == 1);
assert(ab->format->sample_stride == 2);
#else
assert(ab->format->format->channel_count == 2);
assert(ab->format->sample_stride == 4);
#endif
dma_channel_transfer_from_buffer_now(shared_state.dma_channel, ab->buffer->bytes, ab->sample_count);
}
// irq handler for DMA
void __isr __time_critical_func(audio_i2s_dma_irq_handler)() {
#if PICO_AUDIO_I2S_NOOP
assert(false);
#else
uint dma_channel = shared_state.dma_channel;
if (dma_intsx & (1u << dma_channel)) {
dma_intsx = 1u << dma_channel;
DEBUG_PINS_SET(audio_timing, 4);
// free the buffer we just finished
if (shared_state.playing_buffer) {
give_audio_buffer(audio_i2s_consumer, shared_state.playing_buffer);
#ifndef NDEBUG
shared_state.playing_buffer = NULL;
#endif
}
audio_start_dma_transfer();
DEBUG_PINS_CLR(audio_timing, 4);
}
#endif
}
static bool audio_enabled;
void audio_i2s_set_enabled(bool enabled) {
if (enabled != audio_enabled) {
#ifndef NDEBUG
if (enabled)
{
puts("Enabling PIO I2S audio\n");
printf("(on core %d\n", get_core_num());
}
#endif
irq_set_enabled(DMA_IRQ_x, enabled);
if (enabled) {
audio_start_dma_transfer();
}
pio_sm_set_enabled(audio_pio, shared_state.pio_sm, enabled);
audio_enabled = enabled;
}
}