Pi Pico SDK 1.5.0後開始支援CYW4334 bluetooth 驅動程式,本篇文章使用以實做Raspberry Pi Pico W的藍芽喇叭(A2DP Sink)。
使用元件:
- Raspberry Pi Pico W
- MAX98357A DAC
- 3W喇叭
- Ky-040 Rotary encoder
軟體整合使用BTstack a2dp_sink_demo.c範例程式與前篇文章PIO I2S程式Raspberry Pi Pico PIO(Programmable IO) Episode 4: I2S audio (Play WAVE File In SDCard from MAX98357A DAC)來實做一個藍芽喇叭。
a2dp_sink_demo.c已經把GAP, A2DP, AVRCP軟體部份已經完成。
如圖所示,同時使用到Raspberry Pi Pioc W的五大元件同時運作:Core0、Core1、DMA Controller、PIO0與PIO1。
Core0:負責與A2DP Source溝通,把接收到SBC Package送到SBC ring buffer。
Core1:將SBC ring buffer取出SBC資料解碼成PCM後送到PCM ring buffer。
DMA Controller:從Ring buffer取出 PCM送給PIO1。
PIO1: 輸出PCM到I2S DAC由喇叭播出音效。
PIO0:接Rotary Encoder,解譯順時鐘、逆時鐘或按下按鈕。由AVRCP送出Forward, Backward與play/pause指令。
軟體說明:
以不修改a2dp_sink_demo.c程式碼前提下整合。因為a2dp_sink_demo.c使用static變數。因此使用#include到主程式中。
使用在a2dp_sink_demo.c程式中a2dp_sink_demo_a2dp_connection與a2dp_sink_demo_avrcp_connection兩個變數,以便在主程式中檢查連線狀態與送出avrcp指令。
主程式要實做
btastack_audio.h中的struct "btstack_audio_sink_t;",即:
int (*init)(uint8_t channels,
uint32_t samplerate,
void (*playback) (int16_t * buffer, uint16_t num_samples));
uint32_t (*get_samplerate)(void);
void (*set_volume)(uint8_t volume);
void (*start_stream)(void);
void (*stop_stream)(void);
void (*close)(void);
五個callback function與
const btstack_audio_sink_t * btstack_audio_sink_get_instance(void);
void btstack_audio_sink_set_instance(const btstack_audio_sink_t * audio_sink_impl);
兩個functions。
Rotary Encoder的PIO程式製作請參閱前篇說明:
請參閱『成果影片』解說。其他詳細程式碼附於文末。
成果影片:
程式碼:
- picow_bt_speaker.c
#include "hardware/clocks.h" #include "pico/cyw43_arch.h" #include "pico/btstack_cyw43.h" #include "i2s_pio_dma.h" #include "pico/multicore.h" #include "pico/util/queue.h" #include "pio_rotary_encoder.h" #include "btstack_audio.h" #include "a2dp_sink_demo.c" #define CLK_PIN 13 #define DT_PIN 14 #define SW_PIN 15 #define I2S_AUDIO_PIO pio1 #define I2S_AUDIO_SM 1 #define I2S_AUIDO_PIO_BASE 20 /* 20:BCK, 21:LRCK*/ #define I2S_AUDIO_PIO_DIN 22 #define WAVE_DECODE_FRAME 128 #define PCM_STORAGE_SIZE 1024*60 /* 60K for ring buffer */ #define DMA_READ_MULT 4 static uint8_t pcm_audio_storage[PCM_STORAGE_SIZE]; static btstack_ring_buffer_t pcm_audio_ring_buffer; i2s_audio_param_t i2s_audio; // audio_sink_init, start_stream, stop, close a2dp_sink_demo_avrcp_connection_t *bt_avrcp_connection; a2dp_sink_demo_a2dp_connection_t *bt_a2dp_connection; static void (*playback_callback)(int16_t * buffer, uint16_t num_samples); uint8_t dma_buff1[WAVE_DECODE_FRAME*BYTES_PER_FRAME*DMA_READ_MULT]; uint8_t dma_buff2[WAVE_DECODE_FRAME*BYTES_PER_FRAME*DMA_READ_MULT]; uint8_t *dma_read_buff_pt=dma_buff1; uint8_t *dma_write_buff_pt=dma_buff2; uint8_t *tmp_pt; uint32_t ring_buffer_empty_count=0; uint32_t ring_buffer_full_count=0; uint32_t ring_bytes_read=0; uint32_t ring_bytes_write=0; uint32_t ring_bytes; bool bt_audio_playing=false; void i2s_audio_dma_cb() { if (dma_channel_get_irq1_status(i2s_audio.dma_chan_num) ) { dma_channel_acknowledge_irq1(i2s_audio.dma_chan_num); if (bt_audio_playing){ tmp_pt = dma_read_buff_pt; dma_read_buff_pt = dma_write_buff_pt; dma_write_buff_pt = tmp_pt; ring_bytes = ring_bytes_read; ring_bytes_read = ring_bytes_write; ring_bytes_write = ring_bytes; dma_channel_set_trans_count(i2s_audio.dma_chan_num, ring_bytes_write>>DMA_SIZE_16, false); dma_channel_set_read_addr(i2s_audio.dma_chan_num, (uint8_t*)dma_write_buff_pt,true); if (btstack_ring_buffer_empty(&pcm_audio_ring_buffer)) ring_buffer_empty_count++; btstack_ring_buffer_read(&pcm_audio_ring_buffer, dma_read_buff_pt,WAVE_DECODE_FRAME*BYTES_PER_FRAME*DMA_READ_MULT, &ring_bytes_read); } } } void core1_decode() { int write_ok=0; uint8_t buff[WAVE_DECODE_FRAME*BYTES_PER_FRAME]; while(1) { multicore_fifo_pop_blocking(); while (bt_audio_playing) { if (write_ok == 0) { playback_callback((uint16_t*)buff, WAVE_DECODE_FRAME); } else { ring_buffer_full_count++; } write_ok=btstack_ring_buffer_write(&pcm_audio_ring_buffer, buff, WAVE_DECODE_FRAME*BYTES_PER_FRAME); } } } /* audio_sink_init : get bitsPerSample, numberOfChannel and SampleRate*/ int bt_audio_sink_init(uint8_t channels, uint32_t samplerate, void (*playback)(int16_t *buffer, uint16_t num_samples) ){ btstack_assert(playback != NULL); btstack_assert(channels != 0); btstack_ring_buffer_init(&pcm_audio_ring_buffer, pcm_audio_storage, sizeof(pcm_audio_storage)); playback_callback = playback; bt_audio_playing=false; /* check a2dp connection status */ bt_a2dp_connection = &a2dp_sink_demo_a2dp_connection; /* avrcp : forward, backward, play/pause */ bt_avrcp_connection = &a2dp_sink_demo_avrcp_connection; //i2s_audio.bitsPerSample = bt_a2dp_connection->sbc_configuration.block_length; i2s_audio.bitsPerSample = 16; i2s_audio.numberOfChannel = channels; i2s_audio.sampleRate = samplerate; memset(dma_read_buff_pt, 0, WAVE_DECODE_FRAME*BYTES_PER_FRAME*DMA_READ_MULT); memset(dma_write_buff_pt, 0, WAVE_DECODE_FRAME*BYTES_PER_FRAME*DMA_READ_MULT); dma_channel_set_trans_count(i2s_audio.dma_chan_num, 0, false); dma_channel_set_read_addr(i2s_audio.dma_chan_num, (uint8_t*)dma_read_buff_pt,false); i2s_audio.i2s_dma_cb=i2s_audio_dma_cb; i2s_audio_init(I2S_AUDIO_PIO, I2S_AUDIO_SM, I2S_AUIDO_PIO_BASE, I2S_AUDIO_PIO_DIN, &i2s_audio); i2s_audio_enable(I2S_AUDIO_PIO, I2S_AUDIO_SM); multicore_launch_core1(core1_decode); return 1; } void bt_audio_sink_set_volume(uint8_t volume) { UNUSED(volume); } void bt_audio_sink_start_stream() { bt_audio_playing = true; multicore_fifo_push_blocking(1); dma_channel_start(i2s_audio.dma_chan_num); } void bt_audio_sink_stop_stream() { bt_audio_playing=false; dma_channel_wait_for_finish_blocking(i2s_audio.dma_chan_num); pio_sm_clear_fifos(I2S_AUDIO_PIO, I2S_AUDIO_SM); i2s_audio_enable(I2S_AUDIO_PIO, I2S_AUDIO_SM); btstack_ring_buffer_reset(&pcm_audio_ring_buffer); printf("==== ring buffer full:%d, ring buffer empty:%d\n", ring_buffer_full_count, ring_buffer_empty_count); ring_buffer_full_count=0; ring_buffer_empty_count=0; } void bt_audio_sink_close() { i2s_audio_reset(I2S_AUDIO_PIO, I2S_AUDIO_SM); multicore_reset_core1(); } static const btstack_audio_sink_t btstack_audio_sink_instance= { .init = &bt_audio_sink_init, .set_volume = &bt_audio_sink_set_volume, .start_stream = &bt_audio_sink_start_stream, .stop_stream = &bt_audio_sink_stop_stream, .close = &bt_audio_sink_close, }; /** * @brief Get BTstack Audio Sink Instance * @return btstack_audio_sink implementation */ const btstack_audio_sink_t * btstack_audio_sink_get_instance(void){ return &btstack_audio_sink_instance; } uint8_t volume = 50; void rotary_encoder_cb(queue_t* q ) { PIO_RE_QUEUE_T data; if (!queue_is_empty(q)) { queue_remove_blocking(q, &data); printf("action:%d\n",data.action); if (data.action == PIO_RE_CLOCKWISE) { if (bt_avrcp_connection->playing == 0 || bt_avrcp_connection->playing == 1) { avrcp_controller_forward(bt_avrcp_connection->avrcp_cid); } } if (data.action == PIO_RE_COUNTERCLOCKWISE) { if (bt_avrcp_connection->playing == 0 || bt_avrcp_connection->playing == 1) { avrcp_controller_backward(bt_avrcp_connection->avrcp_cid); } } if (data.action == PIO_RE_SWPRESS) { if (bt_avrcp_connection->playing == 0) { avrcp_controller_play(bt_avrcp_connection->avrcp_cid); } if (bt_avrcp_connection->playing == 1) { avrcp_controller_pause(bt_avrcp_connection->avrcp_cid); } } } } int main() { stdio_init_all(); if (cyw43_arch_init()) { puts("cyw43_arch_init error"); return 0; } cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN,false); btstack_main(1, NULL); // call a2dp_sink_demo.c gap_set_local_name("PicoW BT"); pio_rotary_encoder_default_init(); pio_rotary_encoder_cb(rotary_encoder_cb); while(1) { switch(bt_a2dp_connection->stream_state) { case STREAM_STATE_CLOSED: cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, false); break; case STREAM_STATE_OPEN: cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, true); break; case STREAM_STATE_PAUSED: cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, false); break; case STREAM_STATE_PLAYING: cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN,!cyw43_arch_gpio_get(CYW43_WL_GPIO_LED_PIN)); break; } sleep_ms(50); } return 0; }
沒有留言:
張貼留言