-
Notifications
You must be signed in to change notification settings - Fork 69
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #879 from Daft-Freak/pico-standalone-i2s
pico: standalone I2S code
- Loading branch information
Showing
7 changed files
with
152 additions
and
78 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,89 +1,141 @@ | ||
#include <cmath> | ||
|
||
#include "audio.hpp" | ||
#include "config.h" | ||
|
||
#include "hardware/clocks.h" | ||
#include "hardware/dma.h" | ||
#include "hardware/pio.h" | ||
#include "pico/audio_i2s.h" | ||
#define AUDIO_SAMPLE_FREQ 44100 | ||
|
||
#include "audio/audio.hpp" | ||
|
||
#define audio_pio __CONCAT(pio, PICO_AUDIO_I2S_PIO) | ||
#include "i2s.pio.h" | ||
|
||
#define audio_pio __CONCAT(pio, AUDIO_I2S_PIO) | ||
|
||
#define AUDIO_SAMPLE_FREQ 44100 | ||
#define AUDIO_BUFFER_SIZE 256 | ||
#define AUDIO_NUM_BUFFERS 2 | ||
|
||
static int16_t audio_buffer[AUDIO_BUFFER_SIZE * AUDIO_NUM_BUFFERS]; | ||
static volatile int need_refill = AUDIO_NUM_BUFFERS; | ||
static int cur_read_buffer_index = 1; | ||
static int cur_write_buffer_index = 1; | ||
|
||
// track offset if we limit samples pre update | ||
#ifdef AUDIO_MAX_SAMPLE_UPDATE | ||
static int refill_offset = 0; | ||
#endif | ||
|
||
static const int dma_irq = 1; // we usually use DMA_IRQ_0 for display stuff | ||
static int dma_channel; | ||
|
||
static void __not_in_flash_func(i2s_irq_handler)() { | ||
if(!dma_irqn_get_channel_status(dma_irq, dma_channel)) | ||
return; | ||
|
||
dma_irqn_acknowledge_channel(dma_irq, dma_channel); | ||
|
||
static audio_buffer_pool *audio_pool = nullptr; | ||
if(need_refill < AUDIO_NUM_BUFFERS - 1) | ||
need_refill++; | ||
|
||
static struct audio_buffer *cur_buffer = nullptr; | ||
// setup for next buffer | ||
dma_channel_set_read_addr(dma_channel, audio_buffer + cur_read_buffer_index * AUDIO_BUFFER_SIZE, true); | ||
|
||
cur_read_buffer_index = (cur_read_buffer_index + 1) % AUDIO_NUM_BUFFERS; | ||
} | ||
|
||
void init_audio() { | ||
static audio_format_t audio_format = { | ||
.sample_freq = AUDIO_SAMPLE_FREQ, | ||
.format = AUDIO_BUFFER_FORMAT_PCM_S16, | ||
.channel_count = 1 | ||
}; | ||
|
||
static struct audio_buffer_format producer_format = { | ||
.format = &audio_format, | ||
.sample_stride = 2 | ||
}; | ||
|
||
struct audio_buffer_pool *producer_pool = audio_new_producer_pool(&producer_format, 4, 256); | ||
const struct audio_format *output_format; | ||
|
||
uint8_t dma_channel = dma_claim_unused_channel(true); | ||
uint8_t pio_sm = pio_claim_unused_sm(audio_pio, true); | ||
|
||
// audio_i2s_setup claims | ||
dma_channel_unclaim(dma_channel); | ||
pio_sm_unclaim(audio_pio, pio_sm); | ||
|
||
struct audio_i2s_config config = { | ||
.data_pin = PICO_AUDIO_I2S_DATA_PIN, | ||
.clock_pin_base = PICO_AUDIO_I2S_CLOCK_PIN_BASE, | ||
.dma_channel = dma_channel, | ||
.pio_sm = pio_sm, | ||
}; | ||
|
||
output_format = audio_i2s_setup(&audio_format, &config); | ||
if (!output_format) { | ||
panic("PicoAudio: Unable to open audio device.\n"); | ||
} | ||
// setup PIO | ||
|
||
#if AUDIO_I2S_DATA_PIN >= 32 || AUDIO_I2S_CLOCK_PIN_BASE >= 31 | ||
// this assumes anything else using this PIO can also deal with the base | ||
static_assert(AUDIO_I2S_DATA_PIN >= 16 && AUDIO_I2S_CLOCK_PIN_BASE >= 16); | ||
pio_set_gpio_base(audio_pio, 16); | ||
#endif | ||
|
||
int pio_offset = pio_add_program(audio_pio, &i2s_program); | ||
|
||
[[maybe_unused]] bool ok = audio_i2s_connect(producer_pool); | ||
assert(ok); | ||
audio_i2s_set_enabled(true); | ||
int pio_sm = pio_claim_unused_sm(audio_pio, true); | ||
|
||
audio_pool = producer_pool; | ||
pio_sm_config cfg = i2s_program_get_default_config(pio_offset); | ||
|
||
const int bitrate = AUDIO_SAMPLE_FREQ * 16 * 2; | ||
const int clkdiv = clock_get_hz(clk_sys) / float(bitrate * 2); | ||
sm_config_set_clkdiv(&cfg, clkdiv); | ||
|
||
sm_config_set_out_shift(&cfg, false, true, 32); | ||
sm_config_set_out_pins(&cfg, AUDIO_I2S_DATA_PIN, 1); | ||
sm_config_set_fifo_join(&cfg, PIO_FIFO_JOIN_TX); | ||
sm_config_set_sideset_pins(&cfg, AUDIO_I2S_CLOCK_PIN_BASE); | ||
|
||
// init pins | ||
pio_gpio_init(audio_pio, AUDIO_I2S_DATA_PIN); | ||
pio_gpio_init(audio_pio, AUDIO_I2S_CLOCK_PIN_BASE + 0); // bit clock | ||
pio_gpio_init(audio_pio, AUDIO_I2S_CLOCK_PIN_BASE + 1); // left/right clock | ||
|
||
pio_sm_set_consecutive_pindirs(audio_pio, pio_sm, AUDIO_I2S_DATA_PIN, 1, true); | ||
pio_sm_set_consecutive_pindirs(audio_pio, pio_sm, AUDIO_I2S_CLOCK_PIN_BASE, 2, true); | ||
|
||
pio_sm_init(audio_pio, pio_sm, pio_offset, &cfg); | ||
|
||
// setup DMA | ||
dma_channel = dma_claim_unused_channel(true); | ||
|
||
dma_channel_config c; | ||
c = dma_channel_get_default_config(dma_channel); | ||
channel_config_set_dreq(&c, pio_get_dreq(audio_pio, pio_sm, true)); | ||
channel_config_set_transfer_data_size(&c, DMA_SIZE_16); // free mono -> stereo! | ||
dma_channel_configure( | ||
dma_channel, | ||
&c, | ||
&audio_pio->txf[pio_sm], | ||
audio_buffer, | ||
AUDIO_BUFFER_SIZE, | ||
false | ||
); | ||
|
||
// enable irq | ||
dma_irqn_set_channel_enabled(dma_irq, dma_channel, true); | ||
irq_add_shared_handler(DMA_IRQ_0 + dma_irq, i2s_irq_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); | ||
irq_set_enabled(DMA_IRQ_0 + dma_irq, true); | ||
|
||
// start | ||
dma_channel_start(dma_channel); | ||
pio_sm_set_enabled(audio_pio, pio_sm, true); | ||
} | ||
|
||
void update_audio(uint32_t time) { | ||
// attempt to get new buffer | ||
if(!cur_buffer) { | ||
cur_buffer = take_audio_buffer(audio_pool, false); | ||
if(cur_buffer) | ||
cur_buffer->sample_count = 0; | ||
} | ||
|
||
if(cur_buffer) { | ||
auto samples = ((int16_t *)cur_buffer->buffer->bytes) + cur_buffer->sample_count; | ||
if(need_refill) { | ||
auto buffer = audio_buffer + cur_write_buffer_index * AUDIO_BUFFER_SIZE; | ||
|
||
auto max_samples = cur_buffer->max_sample_count - cur_buffer->sample_count; | ||
int count = AUDIO_BUFFER_SIZE; | ||
|
||
#ifdef AUDIO_MAX_SAMPLE_UPDATE | ||
if(max_samples > AUDIO_MAX_SAMPLE_UPDATE) | ||
max_samples = AUDIO_MAX_SAMPLE_UPDATE; | ||
count -= refill_offset; | ||
buffer += refill_offset; | ||
if(count > AUDIO_MAX_SAMPLE_UPDATE) | ||
count = AUDIO_MAX_SAMPLE_UPDATE; | ||
|
||
refill_offset += count; | ||
#endif | ||
|
||
for(uint32_t i = 0; i < max_samples; i += 2) { | ||
int val = (int)blit::get_audio_frame() - 0x8000; | ||
*samples++ = val; | ||
*samples++ = val; | ||
} | ||
for(int i = 0; i < count; i += 2) { | ||
int val = (int)blit::get_audio_frame() - 0x8000; | ||
// 22050 -> 44100 | ||
*buffer++ = val; | ||
*buffer++ = val; | ||
} | ||
|
||
cur_buffer->sample_count += max_samples; | ||
#ifdef AUDIO_MAX_SAMPLE_UPDATE | ||
if(refill_offset < AUDIO_BUFFER_SIZE) | ||
return; | ||
|
||
if(cur_buffer->sample_count == cur_buffer->max_sample_count) { | ||
give_audio_buffer(audio_pool, cur_buffer); | ||
cur_buffer = nullptr; | ||
} | ||
} | ||
refill_offset = 0; | ||
#endif | ||
|
||
cur_write_buffer_index = (cur_write_buffer_index + 1) % AUDIO_NUM_BUFFERS; | ||
|
||
need_refill--; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
.program i2s | ||
; sideset bck, lrclk | ||
.side_set 2 | ||
set x, 14 side 0b11 | ||
|
||
; 16 bits for LRCLK=1 | ||
; 2 16-bit values, R is first/highest | ||
loop1: | ||
out pins, 1 side 0b10 | ||
jmp x--, loop1 side 0b11 | ||
out pins, 1 side 0b00 ; last bit and switch LRCLK | ||
set x, 14 side 0b01 ; prepare for next word | ||
|
||
loop0: | ||
out pins, 1 side 0b00 | ||
jmp x--, loop0 side 0b01 | ||
out pins, 1 side 0b10 ; last bit and switch LRCLK |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters