Skip to content

Commit

Permalink
Merge pull request #879 from Daft-Freak/pico-standalone-i2s
Browse files Browse the repository at this point in the history
pico: standalone I2S code
  • Loading branch information
Daft-Freak authored Dec 19, 2024
2 parents db20fc7 + 0ff8751 commit 699bf4e
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 78 deletions.
7 changes: 2 additions & 5 deletions 32blit-pico/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ target_include_directories(BlitHalPico INTERFACE
)

target_compile_definitions(BlitHalPico INTERFACE
PICO_AUDIO_I2S_MONO_INPUT=1
PICO_AUDIO_DMA_IRQ=1
)

Expand Down Expand Up @@ -110,10 +109,7 @@ if(NOT BLIT_USB_DRIVER)
endif()

# driver dependencies
if(BLIT_AUDIO_DRIVER STREQUAL "i2s")
set(BLIT_REQUIRE_PICO_EXTRAS TRUE)
list(APPEND BLIT_BOARD_LIBRARIES pico_audio_i2s)
elseif(BLIT_AUDIO_DRIVER STREQUAL "pwm")
if(BLIT_AUDIO_DRIVER STREQUAL "pwm")
set(BLIT_REQUIRE_PICO_EXTRAS TRUE)
list(APPEND BLIT_BOARD_LIBRARIES pico_audio_pwm)
endif()
Expand Down Expand Up @@ -150,6 +146,7 @@ pico_sdk_init()
# generate PIO headers (has to be after SDK init)
pico_generate_pio_header(BlitHalPico ${CMAKE_CURRENT_LIST_DIR}/dbi-spi.pio)
pico_generate_pio_header(BlitHalPico ${CMAKE_CURRENT_LIST_DIR}/dbi-8bit.pio)
pico_generate_pio_header(BlitHalPico ${CMAKE_CURRENT_LIST_DIR}/audio/i2s.pio)
pico_generate_pio_header(BlitHalPico ${CMAKE_CURRENT_LIST_DIR}/spi.pio)

# include picovision drivers
Expand Down
180 changes: 116 additions & 64 deletions 32blit-pico/audio/i2s.cpp
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--;
}
}
17 changes: 17 additions & 0 deletions 32blit-pico/audio/i2s.pio
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
7 changes: 0 additions & 7 deletions 32blit-pico/board/pimoroni_picovision/config.cmake
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
set(BLIT_BOARD_NAME "PicoVision")

set(BLIT_BOARD_DEFINITIONS
PICO_AUDIO_I2S_PIO=0
PICO_AUDIO_I2S_DATA_PIN=26
PICO_AUDIO_I2S_CLOCK_PIN_BASE=27
PICO_AUDIO_I2S_BUFFER_SAMPLE_LENGTH=256
)

blit_driver(audio i2s)
blit_driver(display picovision)
blit_driver(input usb_hid)
Expand Down
5 changes: 4 additions & 1 deletion 32blit-pico/board/pimoroni_picovision/config.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#pragma once

#define AUDIO_I2S_DATA_PIN 26
#define AUDIO_I2S_CLOCK_PIN_BASE 27

#define DEFAULT_SCREEN_FORMAT PixelFormat::BGR555

// native
Expand All @@ -11,4 +14,4 @@
#define SD_SCK 10
#define SD_MOSI 11
#define SD_MISO 12
#define SD_CS 15
#define SD_CS 15
1 change: 0 additions & 1 deletion 32blit-pico/board/vgaboard/config.cmake
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
set(BLIT_BOARD_NAME "VGA Board")

set(BLIT_BOARD_DEFINITIONS
PICO_AUDIO_I2S_BUFFER_SAMPLE_LENGTH=256
PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA=1
PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS=12
)
Expand Down
13 changes: 13 additions & 0 deletions 32blit-pico/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@
#endif
#endif

// default i2s config to values from SDK board
#ifndef AUDIO_I2S_DATA_PIN
#define AUDIO_I2S_DATA_PIN PICO_AUDIO_I2S_DATA_PIN
#endif

#ifndef AUDIO_I2S_CLOCK_PIN_BASE
#define AUDIO_I2S_CLOCK_PIN_BASE PICO_AUDIO_I2S_CLOCK_PIN_BASE
#endif

#ifndef AUDIO_I2S_PIO
#define AUDIO_I2S_PIO 0
#endif

#ifndef BUTTON_LEFT_PIN
#define BUTTON_LEFT_PIN -1
#define BUTTON_LEFT_BI_DECL
Expand Down

0 comments on commit 699bf4e

Please sign in to comment.