diff --git a/32blit-pico/CMakeLists.txt b/32blit-pico/CMakeLists.txt index 6168e3090..28053a1d0 100644 --- a/32blit-pico/CMakeLists.txt +++ b/32blit-pico/CMakeLists.txt @@ -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 ) @@ -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() @@ -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 diff --git a/32blit-pico/audio/i2s.cpp b/32blit-pico/audio/i2s.cpp index 9a57c4804..25482155b 100644 --- a/32blit-pico/audio/i2s.cpp +++ b/32blit-pico/audio/i2s.cpp @@ -1,89 +1,141 @@ +#include + #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--; + } } diff --git a/32blit-pico/audio/i2s.pio b/32blit-pico/audio/i2s.pio new file mode 100644 index 000000000..37e7f2d8b --- /dev/null +++ b/32blit-pico/audio/i2s.pio @@ -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 diff --git a/32blit-pico/board/pimoroni_picovision/config.cmake b/32blit-pico/board/pimoroni_picovision/config.cmake index c698af7ba..ac97cdb62 100644 --- a/32blit-pico/board/pimoroni_picovision/config.cmake +++ b/32blit-pico/board/pimoroni_picovision/config.cmake @@ -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) diff --git a/32blit-pico/board/pimoroni_picovision/config.h b/32blit-pico/board/pimoroni_picovision/config.h index 768280419..4e3b304a9 100644 --- a/32blit-pico/board/pimoroni_picovision/config.h +++ b/32blit-pico/board/pimoroni_picovision/config.h @@ -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 @@ -11,4 +14,4 @@ #define SD_SCK 10 #define SD_MOSI 11 #define SD_MISO 12 -#define SD_CS 15 \ No newline at end of file +#define SD_CS 15 diff --git a/32blit-pico/board/vgaboard/config.cmake b/32blit-pico/board/vgaboard/config.cmake index 27fcf6ce5..cc4d244ad 100644 --- a/32blit-pico/board/vgaboard/config.cmake +++ b/32blit-pico/board/vgaboard/config.cmake @@ -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 ) diff --git a/32blit-pico/config.h b/32blit-pico/config.h index e13bf1d89..a5f948d27 100644 --- a/32blit-pico/config.h +++ b/32blit-pico/config.h @@ -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