prettyprint

2022年4月6日 星期三

ESP-IDF: ESP32 Bluetooth stereo Speaker with Backward/Play(Pause)/Forward button

本實驗製作一個藍芽喇叭,並附有前一首/下一首,播放/暫停控制鍵。

一、使用元件:

  1. ESP32-S(38 pins)開發版
  2. MAX98357A
  3. 3W喇叭
  4. 按鈕

二、成果展示:




三、軟體開發環境:

  1. VS Code
  2. ESP-IDF extension

四、Bluetooth classic Audio:

ESP32 Bluetooth 架構主要參考文件為下列官方網址:
https://www.espressif.com/sites/default/files/documentation/esp32_bluetooth_architecture_en.pdf
bluetooth audio 使用bluetooth classic A2DP and AVRCP因此只能使用Bluedroid,架構如下
 
memuconfig的bluetooth選項如下:

bluetooth audio 使用A2DP與AVRCP remote control,其profile相關如下圖所示:
依此結構,軟體初始化順序為
controller: init/enable --> bluedroid init/enable --> AVRCP register callback/init --> A2DP register cb --> A2DP sink register cb and data cb/init --> GAP register cb, security param, set device name and scan mode:詳細程式碼如文末所附程式碼(或成果展示影片的詳細步驟)。相關API參考網址:
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/bluetooth/classic_bt.html

五、ESP32 使用的GPIO Pins:

  1. Buttons and LEDs
  2. I2S pins:

六、程式碼:
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_bt.h"
#include "esp_bt_device.h"
#include "esp_bt_main.h"
#include "esp_a2dp_api.h"
#include "esp_avrc_api.h"
#include "esp_gap_bt_api.h"
#include "string.h"
#include "nvs_flash.h"
#include "driver/i2s.h"
#include "driver/gpio.h"
#include "esp_log.h"

#define BUTTON_FORWARD_PIN           5
#define BUTTON_PLAY_PAUSE_PIN        23
#define BUTTON_BACKWARD_PIN          19
#define LED_RED_PIN                  17
#define LED_GREEN_PIN                16
#define TAG "MY_BT_SPEAKER"

uint8_t my_bt_speaker_state=ESP_AVRC_PLAYBACK_STOPPED;
const char bt_device_name[]="MY_BT_SPEAKER";
bool avrc_conn=false;

static bool ct_button_play_pause_press=false;
static bool ct_button_backward_press=false;
static bool ct_button_forward_press=false;

void my_bt_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) {
    switch (event) {
        case ESP_BT_GAP_DISC_RES_EVT:
        break;
        case ESP_BT_GAP_DISC_STATE_CHANGED_EVT:
        break;
        case ESP_BT_GAP_RMT_SRVCS_EVT:
        break;
        case ESP_BT_GAP_RMT_SRVC_REC_EVT:
        break;
        case ESP_BT_GAP_AUTH_CMPL_EVT:
            ESP_LOGI(TAG,"auth: state:%d, remote_name:%s", param->auth_cmpl.stat, param->auth_cmpl.device_name);
        break;
        case ESP_BT_GAP_PIN_REQ_EVT:
        break;
        case ESP_BT_GAP_CFM_REQ_EVT:
            esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
        break;
        case ESP_BT_GAP_KEY_NOTIF_EVT:
        break;
        case ESP_BT_GAP_MODE_CHG_EVT:
        break;
        default: 
            ESP_LOGI(TAG, "GAP EVENT ID:%d",event);
        break;
    }
}


void my_bt_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) {
    switch(event) {
        case ESP_A2D_CONNECTION_STATE_EVT:
        if (param->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED) {
            esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
            gpio_set_level(LED_GREEN_PIN, 1);
            gpio_set_level(LED_RED_PIN, 0);
        }
        if (param->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
            esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
            gpio_set_level(LED_GREEN_PIN, 0);
            gpio_set_level(LED_RED_PIN, 1);
        }
        break;
        case ESP_A2D_AUDIO_STATE_EVT:
        break;
        case ESP_A2D_AUDIO_CFG_EVT:
        if (param->audio_cfg.mcc.type == ESP_A2D_MCT_SBC) {
            int sample_rate = 16000;
            char oct0 = param->audio_cfg.mcc.cie.sbc[0];
            if (oct0 & (0x01 << 6)) {
                sample_rate = 32000;
            } else if (oct0 & (0x01 << 5)) {
                sample_rate = 44100;
            } else if (oct0 & (0x01 << 4)) {
                sample_rate = 48000;
            }
            i2s_set_clk(0, sample_rate, 16, 2);
        }
            
        break;
        case ESP_A2D_MEDIA_CTRL_ACK_EVT:
        break;
        case ESP_A2D_PROF_STATE_EVT:
        break;
        default:
        break;
    }
}

void my_bt_a2d_sink_data_cb(const uint8_t *buf, uint32_t len) {
    size_t wb;
    i2s_write(0, buf, len, &wb, portMAX_DELAY);
}

esp_avrc_rn_evt_cap_mask_t rn_cap_mask={0};
void my_bt_avrc_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param) {
    char *text;
    switch(event) {
        case ESP_AVRC_CT_CONNECTION_STATE_EVT:
        if (param->conn_stat.connected) {
            esp_avrc_ct_send_get_rn_capabilities_cmd(1);
            avrc_conn=1;
            esp_avrc_ct_send_register_notification_cmd(1, ESP_AVRC_RN_PLAY_STATUS_CHANGE, 0);
        } else {
            rn_cap_mask.bits=0;
             avrc_conn=0;
        }
        break;
        case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT:
        break;
        case ESP_AVRC_CT_CHANGE_NOTIFY_EVT:
            switch(param->change_ntf.event_id) {
                case ESP_AVRC_RN_VOLUME_CHANGE:
                break;
                case ESP_AVRC_RN_PLAY_STATUS_CHANGE:
                    my_bt_speaker_state = param->change_ntf.event_parameter.playback;
                break;
                default:
                break;
            }
        break;
        case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT:
            rn_cap_mask.bits = param->get_rn_caps_rsp.evt_set.bits;
            break;
        case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT:
        break;
        case ESP_AVRC_CT_METADATA_RSP_EVT:
            text = (char*)param->meta_rsp.attr_text;
            text[param->meta_rsp.attr_length]='\0';
            ESP_LOGI(TAG,"metadata_rsp:%d, %s, %d", param->meta_rsp.attr_id, text, param->meta_rsp.attr_length);
        break;
        default:
        break;
    }
}
static void ct_play_forward(void *param) {
    if (!ct_button_forward_press) {
        ct_button_forward_press=true;
    }
}
static void ct_play_play_pause(void *param) {
    if (!ct_button_play_pause_press) {
        ct_button_play_pause_press=true;
    }
}
static void ct_play_backward(void *param) {
    if (!ct_button_backward_press) {
        ct_button_backward_press=true;
    }
}
void my_bt_set_gpio_pins() {
    gpio_set_direction(BUTTON_BACKWARD_PIN, GPIO_MODE_INPUT);
    gpio_set_direction(BUTTON_FORWARD_PIN, GPIO_MODE_INPUT);
    gpio_set_direction(BUTTON_PLAY_PAUSE_PIN, GPIO_MODE_INPUT);
    gpio_set_direction(LED_GREEN_PIN, GPIO_MODE_OUTPUT);
    gpio_set_direction(LED_RED_PIN, GPIO_MODE_OUTPUT);

    gpio_set_intr_type(BUTTON_BACKWARD_PIN, GPIO_INTR_POSEDGE);
    gpio_set_intr_type(BUTTON_FORWARD_PIN, GPIO_INTR_POSEDGE);
    gpio_set_intr_type(BUTTON_PLAY_PAUSE_PIN, GPIO_INTR_POSEDGE);
    

    gpio_install_isr_service(ESP_INTR_FLAG_EDGE);
    gpio_isr_handler_add(BUTTON_FORWARD_PIN, ct_play_forward, 0);
    gpio_isr_handler_add(BUTTON_PLAY_PAUSE_PIN, ct_play_play_pause, 0);
    gpio_isr_handler_add(BUTTON_BACKWARD_PIN, ct_play_backward, 0);

    gpio_set_level(LED_GREEN_PIN,0);
    gpio_set_level(LED_RED_PIN,1);
}
void app_main(void)
{
    esp_err_t err;
    err = nvs_flash_init();
    if (err != ESP_OK) {
        ESP_LOGI(TAG, "nvs init error");
        return;
    }

    my_bt_set_gpio_pins();

    i2s_config_t i2s_config = {
        .mode = I2S_MODE_MASTER | I2S_MODE_TX,                          // Only TX
        .sample_rate = 44100,
        .bits_per_sample = 16,
        .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,                   //2-channels
        .communication_format = I2S_COMM_FORMAT_STAND_I2S,              // MAX98357A I2S Philips
        .dma_buf_count = 6,
        .dma_buf_len = 60,
        .intr_alloc_flags = 0,                                          //Default interrupt priority
        .tx_desc_auto_clear = true                                      //Auto clear tx descriptor on underflow
    };
    i2s_driver_install(0, &i2s_config, 0, NULL);

    i2s_pin_config_t pin_config = {
        .bck_io_num = 26,
        .ws_io_num = 22,
        .data_out_num = 25,
        .data_in_num = -1                                                       //Not used
    };

    i2s_set_pin(0, &pin_config);

    esp_bt_controller_config_t bt_controller  = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    err = esp_bt_controller_init(&bt_controller);
    err = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT);

    err= esp_bluedroid_init();
    err = esp_bluedroid_enable();

    err = esp_avrc_ct_register_callback(my_bt_avrc_cb);
    err = esp_avrc_ct_init();
   
    err = esp_a2d_register_callback(my_bt_a2d_cb);
    err = esp_a2d_sink_register_data_callback(my_bt_a2d_sink_data_cb);
    err = esp_a2d_sink_init();

    err = esp_bt_gap_register_callback(my_bt_gap_cb);
    err = esp_bt_dev_set_device_name(bt_device_name);
    esp_bt_io_cap_t io_cap=ESP_BT_IO_CAP_IO;
    err = esp_bt_gap_set_security_param(ESP_BT_SP_IOCAP_MODE, &io_cap, sizeof(io_cap));
    err = esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
   
   while(1) {
        if (avrc_conn) {
            vTaskDelay(5);
            if (ct_button_backward_press) {
                esp_avrc_ct_send_passthrough_cmd(1, ESP_AVRC_PT_CMD_BACKWARD, ESP_AVRC_PT_CMD_STATE_PRESSED);
                vTaskDelay(300/portTICK_PERIOD_MS);
                //esp_avrc_ct_send_metadata_cmd(1, ESP_AVRC_MD_ATTR_TITLE);
                ct_button_backward_press=false;
            } 
            if (ct_button_forward_press) {
                esp_avrc_ct_send_passthrough_cmd(1, ESP_AVRC_PT_CMD_FORWARD, ESP_AVRC_PT_CMD_STATE_PRESSED);
                vTaskDelay(300/portTICK_PERIOD_MS);
                //esp_avrc_ct_send_metadata_cmd(1, ESP_AVRC_MD_ATTR_TITLE);
                ct_button_forward_press=false;
            } 
            if (ct_button_play_pause_press) {
                if (my_bt_speaker_state == ESP_AVRC_PLAYBACK_STOPPED || my_bt_speaker_state == ESP_AVRC_PLAYBACK_PAUSED) {
                    esp_avrc_ct_send_passthrough_cmd(1, ESP_AVRC_PT_CMD_PLAY, ESP_AVRC_PT_CMD_STATE_PRESSED);
                    my_bt_speaker_state = ESP_AVRC_PLAYBACK_PLAYING;
                } else  if (my_bt_speaker_state == ESP_AVRC_PLAYBACK_PLAYING) {
                    esp_avrc_ct_send_passthrough_cmd(1, ESP_AVRC_PT_CMD_PAUSE, ESP_AVRC_PT_CMD_STATE_PRESSED);
                    my_bt_speaker_state = ESP_AVRC_PLAYBACK_PAUSED;
                }
                //esp_avrc_ct_send_metadata_cmd(1, ESP_AVRC_MD_ATTR_TITLE);
                vTaskDelay(300/portTICK_PERIOD_MS);
                ct_button_play_pause_press=false;
            } 
        } else {
            vTaskDelay(5);
        }
    }
}


沒有留言:

張貼留言