From c4985d3789d4378e44e0a6ab15999f4126c8c37d Mon Sep 17 00:00:00 2001 From: Kevin J Walters Date: Sat, 30 Dec 2023 20:35:28 +0000 Subject: [PATCH] Making Bluetooth Great Again GalacticUnicorn init now takes optional audio rate and pio to allow audio to be disabled and pio selected to avoid clashes. Fixing Bluetooth device name which was broken by some carelessness in cmake renovation. --- CMakeLists.txt | 11 +- .../galactic_unicorn/galactic_unicorn.cpp | 134 +++++++++++------- .../galactic_unicorn/galactic_unicorn.hpp | 14 +- uad/audiosourcebluetooth.cpp | 8 ++ uad/unicornaudiodisplay.hpp | 24 +++- 5 files changed, 123 insertions(+), 68 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c7d75f4..40849c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -105,18 +105,23 @@ target_link_libraries(${NAME2} hardware_dma ) -message(NOTICE "Display: ${DISPLAY_NAME}") +# Create the Bluetooth device name with upper case first character +string(SUBSTRING ${UNICORN_MODEL} 0 1 FIRST_LETTER) +string(TOUPPER ${FIRST_LETTER} FIRST_LETTER) +string(REGEX REPLACE "^.(.*)" "${FIRST_LETTER}\\1 Unicorn" BLUETOOTH_DEVICE_NAME "${UNICORN_MODEL}") + +message(NOTICE "Bluetooth device name: ${BLUETOOTH_DEVICE_NAME}") target_compile_definitions(${NAME1} PRIVATE PICO_AUDIO_I2S_DATA_PIN=9 PICO_AUDIO_I2S_CLOCK_PIN_BASE=10 - BLUETOOTH_DEVICE_NAME="${DISPLAY_NAME}" + BLUETOOTH_DEVICE_NAME="${BLUETOOTH_DEVICE_NAME}" AUDIO_SOURCES=AudioSourceBluetooth ) target_compile_definitions(${NAME2} PRIVATE PICO_AUDIO_I2S_DATA_PIN=9 PICO_AUDIO_I2S_CLOCK_PIN_BASE=10 - BLUETOOTH_DEVICE_NAME="${DISPLAY_NAME}" + BLUETOOTH_DEVICE_NAME="${BLUETOOTH_DEVICE_NAME}" AUDIO_SOURCES=AudioSourceDMAADC ) diff --git a/libraries/galactic_unicorn/galactic_unicorn.cpp b/libraries/galactic_unicorn/galactic_unicorn.cpp index 28af47f..f57d791 100644 --- a/libraries/galactic_unicorn/galactic_unicorn.cpp +++ b/libraries/galactic_unicorn/galactic_unicorn.cpp @@ -1,6 +1,13 @@ #include -#include - +#include +#include + +#include "pico.h" +#include "hardware/address_mapped.h" +#include "hardware/structs/pio.h" +#include "hardware/gpio.h" +#include "hardware/regs/dreq.h" +#include "hardware/pio_instructions.h" #include "hardware/dma.h" #include "hardware/irq.h" #include "hardware/adc.h" @@ -48,10 +55,10 @@ static uint32_t audio_dma_channel; namespace pimoroni { GalacticUnicorn* GalacticUnicorn::unicorn = nullptr; - PIO GalacticUnicorn::bitstream_pio = pio0; + PIO GalacticUnicorn::bitstream_pio = nullptr; uint GalacticUnicorn::bitstream_sm = 0; uint GalacticUnicorn::bitstream_sm_offset = 0; - PIO GalacticUnicorn::audio_pio = pio0; + PIO GalacticUnicorn::audio_pio = nullptr; uint GalacticUnicorn::audio_sm = 0; uint GalacticUnicorn::audio_sm_offset = 0; @@ -75,13 +82,15 @@ namespace pimoroni { pio_sm_unclaim(bitstream_pio, bitstream_sm); pio_remove_program(bitstream_pio, &galactic_unicorn_program, bitstream_sm_offset); - dma_channel_unclaim(audio_dma_channel); // This works now the teardown behaves correctly - pio_sm_unclaim(audio_pio, audio_sm); - pio_remove_program(audio_pio, &audio_i2s_program, audio_sm_offset); - //irq_remove_handler(DMA_IRQ_0, dma_complete); - irq_remove_handler(DMA_IRQ_1, dma_complete); + if (audio_rate > 0) { + dma_channel_unclaim(audio_dma_channel); // This works now the teardown behaves correctly + pio_sm_unclaim(audio_pio, audio_sm); + pio_remove_program(audio_pio, &audio_i2s_program, audio_sm_offset); + //irq_remove_handler(DMA_IRQ_0, dma_complete); + irq_remove_handler(DMA_IRQ_1, dma_complete); - unicorn = nullptr; + unicorn = nullptr; + } } } @@ -103,15 +112,17 @@ namespace pimoroni { dma_safe_abort(dma_channel); - // Stop the audio SM - pio_sm_set_enabled(audio_pio, audio_sm, false); - - // Reset the I2S pins to avoid popping when audio is suddenly stopped - const uint pins_to_clear = 1 << I2S_DATA | 1 << I2S_BCLK | 1 << I2S_LRCLK; - pio_sm_set_pins_with_mask(audio_pio, audio_sm, 0, pins_to_clear); - - // Abort any in-progress DMA transfer - dma_safe_abort(audio_dma_channel); + // Stop the audio SM if enabled + if (audio_rate > 0) { + pio_sm_set_enabled(audio_pio, audio_sm, false); + + // Reset the I2S pins to avoid popping when audio is suddenly stopped + const uint pins_to_clear = 1 << I2S_DATA | 1 << I2S_BCLK | 1 << I2S_LRCLK; + pio_sm_set_pins_with_mask(audio_pio, audio_sm, 0, pins_to_clear); + + // Abort any in-progress DMA transfer + dma_safe_abort(audio_dma_channel); + } } uint16_t GalacticUnicorn::light() { @@ -119,13 +130,19 @@ namespace pimoroni { return adc_read(); } - void GalacticUnicorn::init() { + void GalacticUnicorn::init(uint audio_rate_, PIO pio) { if(unicorn != nullptr) { // Tear down the old GU instance's hardware resources partial_teardown(); } - + + audio_rate = audio_rate_; + bitstream_pio = pio; + if (audio_rate > 0) { + audio_pio = pio; + } + // for each row: // for each bcd frame: // 0: 00110110 // row pixel count (minus one) @@ -233,7 +250,6 @@ namespace pimoroni { gpio_init(SWITCH_VOLUME_DOWN); gpio_pull_up(SWITCH_VOLUME_DOWN); // setup the pio if it has not previously been set up - bitstream_pio = pio0; if(unicorn == nullptr) { bitstream_sm = pio_claim_unused_sm(bitstream_pio, true); bitstream_sm_offset = pio_add_program(bitstream_pio, &galactic_unicorn_program); @@ -310,38 +326,38 @@ namespace pimoroni { // start the control channel dma_start_channel_mask(1u << dma_ctrl_channel); - - // setup audio pio program - audio_pio = pio0; - if(unicorn == nullptr) { - audio_sm = pio_claim_unused_sm(audio_pio, true); - audio_sm_offset = pio_add_program(audio_pio, &audio_i2s_program); - } - - pio_gpio_init(audio_pio, I2S_DATA); - pio_gpio_init(audio_pio, I2S_BCLK); - pio_gpio_init(audio_pio, I2S_LRCLK); - - audio_i2s_program_init(audio_pio, audio_sm, audio_sm_offset, I2S_DATA, I2S_BCLK); - uint32_t system_clock_frequency = clock_get_hz(clk_sys); - uint32_t divider = system_clock_frequency * 4 / SYSTEM_FREQ; // avoid arithmetic overflow - pio_sm_set_clkdiv_int_frac(audio_pio, audio_sm, divider >> 8u, divider & 0xffu); - - audio_dma_channel = dma_claim_unused_channel(true); - dma_channel_config audio_config = dma_channel_get_default_config(audio_dma_channel); - channel_config_set_transfer_data_size(&audio_config, DMA_SIZE_16); - //channel_config_set_bswap(&audio_config, false); // byte swap to reverse little endian - channel_config_set_dreq(&audio_config, pio_get_dreq(audio_pio, audio_sm, true)); - dma_channel_configure(audio_dma_channel, &audio_config, &audio_pio->txf[audio_sm], NULL, 0, false); - - //dma_channel_set_irq0_enabled(audio_dma_channel, true); - dma_channel_set_irq1_enabled(audio_dma_channel, true); - - if(unicorn == nullptr) { - //irq_add_shared_handler(DMA_IRQ_0, dma_complete, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); - //irq_set_enabled(DMA_IRQ_0, true); - irq_add_shared_handler(DMA_IRQ_1, dma_complete, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); - irq_set_enabled(DMA_IRQ_1, true); + // setup audio pio program if enabled + if (audio_rate > 0) { + if(unicorn == nullptr) { + audio_sm = pio_claim_unused_sm(audio_pio, true); + audio_sm_offset = pio_add_program(audio_pio, &audio_i2s_program); + } + + pio_gpio_init(audio_pio, I2S_DATA); + pio_gpio_init(audio_pio, I2S_BCLK); + pio_gpio_init(audio_pio, I2S_LRCLK); + + audio_i2s_program_init(audio_pio, audio_sm, audio_sm_offset, I2S_DATA, I2S_BCLK); + uint32_t system_clock_frequency = clock_get_hz(clk_sys); + uint32_t divider = system_clock_frequency * 4 / audio_rate; // avoid arithmetic overflow + pio_sm_set_clkdiv_int_frac(audio_pio, audio_sm, divider >> 8u, divider & 0xffu); + + audio_dma_channel = dma_claim_unused_channel(true); + dma_channel_config audio_config = dma_channel_get_default_config(audio_dma_channel); + channel_config_set_transfer_data_size(&audio_config, DMA_SIZE_16); + //channel_config_set_bswap(&audio_config, false); // byte swap to reverse little endian + channel_config_set_dreq(&audio_config, pio_get_dreq(audio_pio, audio_sm, true)); + dma_channel_configure(audio_dma_channel, &audio_config, &audio_pio->txf[audio_sm], NULL, 0, false); + + //dma_channel_set_irq0_enabled(audio_dma_channel, true); + dma_channel_set_irq1_enabled(audio_dma_channel, true); + + if(unicorn == nullptr) { + //irq_add_shared_handler(DMA_IRQ_0, dma_complete, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); + //irq_set_enabled(DMA_IRQ_0, true); + irq_add_shared_handler(DMA_IRQ_1, dma_complete, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); + irq_set_enabled(DMA_IRQ_1, true); + } } unicorn = this; @@ -379,6 +395,8 @@ namespace pimoroni { } void GalacticUnicorn::play_sample(uint8_t *data, uint32_t length) { + if (audio_rate == 0) { return; } + stop_playing(); if(unicorn == this) { @@ -390,6 +408,8 @@ namespace pimoroni { } void GalacticUnicorn::play_synth() { + if (audio_rate == 0) { return; } + if(play_mode != PLAYING_SYNTH) { stop_playing(); } @@ -410,6 +430,8 @@ namespace pimoroni { } void GalacticUnicorn::next_audio_sequence() { + if (audio_rate == 0) { return; } + // Clear any interrupt request caused by our channel //dma_channel_acknowledge_irq0(audio_dma_channel); // NOTE Temporary replacement of the above until this reaches pico-sdk main: @@ -429,6 +451,8 @@ namespace pimoroni { } void GalacticUnicorn::populate_next_synth() { + if (audio_rate == 0) { return; } + int16_t *samples = tone_buffers[current_buffer]; for(uint i = 0; i < TONE_BUFFER_SIZE; i++) { samples[i] = synth.get_audio_frame(); @@ -436,7 +460,7 @@ namespace pimoroni { } void GalacticUnicorn::stop_playing() { - if(unicorn == this) { + if(unicorn == this && audio_rate != 0) { // Stop the audio SM pio_sm_set_enabled(audio_pio, audio_sm, false); diff --git a/libraries/galactic_unicorn/galactic_unicorn.hpp b/libraries/galactic_unicorn/galactic_unicorn.hpp index 3e9c800..706b850 100644 --- a/libraries/galactic_unicorn/galactic_unicorn.hpp +++ b/libraries/galactic_unicorn/galactic_unicorn.hpp @@ -1,6 +1,13 @@ #pragma once +#include "pico.h" +#include "hardware/address_mapped.h" +#include "hardware/structs/pio.h" +#include "hardware/gpio.h" +#include "hardware/regs/dreq.h" +#include "hardware/pio_instructions.h" #include "hardware/pio.h" + #include "pico_graphics.hpp" #include "common/pimoroni_common.hpp" #include "../pico_synth/pico_synth.hpp" @@ -53,7 +60,7 @@ namespace pimoroni { static const uint32_t BCD_FRAME_BYTES = 60; static const uint32_t ROW_BYTES = BCD_FRAME_COUNT * BCD_FRAME_BYTES; static const uint32_t BITSTREAM_LENGTH = (ROW_COUNT * ROW_BYTES); - static const uint SYSTEM_FREQ = 22050; + static const uint DEFAULT_SYSTEM_FREQ = 22050; private: static PIO bitstream_pio; @@ -64,6 +71,7 @@ namespace pimoroni { static uint audio_sm; static uint audio_sm_offset; + uint audio_rate = 0; uint16_t brightness = 256; uint16_t volume = 127; @@ -91,7 +99,7 @@ namespace pimoroni { public: ~GalacticUnicorn(); - void init(); + void init(uint audio_rate_ = DEFAULT_SYSTEM_FREQ, PIO = pio0); static inline void pio_program_init(PIO pio, uint sm, uint offset); void clear(); @@ -126,4 +134,4 @@ namespace pimoroni { void populate_next_synth(); }; -} +} diff --git a/uad/audiosourcebluetooth.cpp b/uad/audiosourcebluetooth.cpp index 95c2a6e..e13cc80 100644 --- a/uad/audiosourcebluetooth.cpp +++ b/uad/audiosourcebluetooth.cpp @@ -69,6 +69,7 @@ static audio_buffer_pool_t *init_audio(uint32_t sample_frequency, uint8_t channe // num channels requested by application btstack_audio_pico_channel_count = channel_count; + printf("init_audio 1\n"); // This is the output format for i2s and is passed to pico-extras audio_i2s_setup // always use stereo btstack_audio_pico_audio_format.format = AUDIO_BUFFER_FORMAT_PCM_S16; @@ -88,17 +89,24 @@ static audio_buffer_pool_t *init_audio(uint32_t sample_frequency, uint8_t channe config.clock_pin_base = PICO_AUDIO_I2S_CLOCK_PIN_BASE; config.dma_channel = (int8_t) dma_claim_unused_channel(true); config.pio_sm = 0; + printf("init_audio 2\n"); + sleep_ms(100); // audio_i2s_setup claims the channel again https://github.com/raspberrypi/pico-extras/issues/48 dma_channel_unclaim(config.dma_channel); + printf("init_audio 3\n"); + sleep_ms(100); const audio_format_t * output_format = audio_i2s_setup(&btstack_audio_pico_audio_format, &config); if (!output_format) { panic("PicoAudio: Unable to open audio device.\n"); } + printf("init_audio 4\n"); + sleep_ms(100); bool ok = audio_i2s_connect(producer_pool); assert(ok); (void)ok; + printf("init_audio 5\n"); return producer_pool; } diff --git a/uad/unicornaudiodisplay.hpp b/uad/unicornaudiodisplay.hpp index ad62bdc..eff5393 100644 --- a/uad/unicornaudiodisplay.hpp +++ b/uad/unicornaudiodisplay.hpp @@ -14,6 +14,13 @@ #include #include +#include "pico.h" +#include "hardware/address_mapped.h" +#include "hardware/structs/pio.h" +#include "hardware/gpio.h" +#include "hardware/regs/dreq.h" +#include "hardware/pio_instructions.h" + #include "pico_graphics.hpp" #include "bitmap_fonts.hpp" #include "font8_data.hpp" @@ -101,7 +108,10 @@ class UnicornAudioDisplay { } effects[current_effect]->start(); - display.init(); + // 0 is audio_rate - this disables audio (I2S/PIO/DMA/IRQ code) + // as I2S audio is handled in AudioSourceBluetooth + // and things don't work if pio0 is used + display.init(0, pio1); display.clear(); graphics.set_font(uad_font_ptr); @@ -128,7 +138,7 @@ class UnicornAudioDisplay { uint32_t rate = sources[current_source]->getSampleRate(); if (rate > 0) { setSampleRate(rate); - } + } }; void run() { sources[current_source]->run(); }; @@ -157,7 +167,7 @@ class UnicornAudioDisplay { void checkButtons(void) { absolute_time_t now_t = get_absolute_time(); - + // Button A: next effect if (button_a.pressedSince(now_t)) { effects[current_effect]->stop(); @@ -172,18 +182,18 @@ class UnicornAudioDisplay { // wait for button release } } - + // Button B: pass to Effect if (button_b.pressedSince(now_t)) { effects[current_effect]->buttonB(); } - + // Button C: pass to Effect and currently the next FFT scale if (button_c.pressedSince(now_t)) { effects[current_effect]->buttonC(); // TODO - finish off moving scale change to effects fft.next_scale(); // TODO - would be better to call an effect method and let it handle this } - + // Button D: shift button to change volume to scale adjustment if (button_d.pressed()) { if (button_volume_down.pressedSince(now_t)) { @@ -200,7 +210,7 @@ class UnicornAudioDisplay { } } } - + if (button_volume_down.pressedSince(now_t) && local_volume >= 16) { local_volume -= 16; } else if (button_volume_up.pressedSince(now_t) && local_volume <= MAX_LOCAL_VOLUME - 16) {