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;
}
沒有留言:
張貼留言