prettyprint

2023年3月10日 星期五

[Raspberry Pi Pico (c-sdk)] BTStack: Ep 1. A2DP, AVRCP, GAP and Bluetooth Speaker

 Pi Pico SDK 1.5.0後開始支援CYW4334 bluetooth 驅動程式,本篇文章使用以實做Raspberry Pi Pico W的藍芽喇叭(A2DP Sink)。

使用元件:

  1. Raspberry Pi Pico W 
  2. MAX98357A DAC
  3. 3W喇叭
  4. 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軟體部份已經完成。

本文章介紹如何整合到前篇PIO I2S播放器。資料流程如下圖:


如圖所示,同時使用到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;
}

沒有留言:

張貼留言