Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pico: standalone I2S code #879

Merged
merged 2 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading