Top.Mail.Ru
ESP32 Bluetooth A2D...
 
Уведомления
Очистить все

ESP32 Bluetooth A2DP + SPI STM32

(@13radeon)
Level 1

Добрый день! Поставил задачу передать музыку на BT наушники. Связка такая SD_Flash + STM32F405 -> SPI -> ESP32 Wroom32D. С карты памяти передаю данные WAV они же PCM на ESP. На ESP запустил пример по работе A2DP source. В колбеке где загружаются данные для CBS подкидываю два буфера поочередно. То есть пока один подгружается в CBS, другой заполняю новыми данными с SPI... Музыка играет и даже в правильном темпе, но вот ясно слышно шумы в момент подгрузки данных! Хоть ты тресни! Разумеется для обслуживания синего зуба одна задача, а для подгрузки по SPI другая задача. Задачи разнесены по ядрам. Но создается впечатление, что где то в недрах A2DP, где из внутреннего буфера подкидываются данные в CBS возникает прерывание от моего SPI / DMA и в CBS залетает какой то мусор... Грешил, что может возникает лаг в цепочке "Попросить новые данные от STM -> Выслать новые данные", думал может не хватает скорости или еще чего... Но выяснилось, что и с вдвое большей скоростью тракт подгрузки справляется. Быть может подскажете в какую сторону копать?!

Краткий пример моего мейна привожу ниже:

/*
Каждый буфер передается в 4 обратных вызова bt_app_a2d_data_cb. Каждый обратный вызов берет 256 байт (int16_t) из буфера. Пока мы отправляем SPI_BUF_1 в CBS, я загружаю новые данные через SPI в SPI_BUF_2. И наоборот...
*/



/*Global variables*/
uint16_t SPI_BUF_1[1024];
uint16_t SPI_BUF_2[1024];
uint8_t flag_buf_Ready_1 = 0, flag_buf_Ready_2 = 0;  
uint8_t flag_buf = 0;
uint16_t cnt_send=0;
uint8_t flag_data=0;



static int32_t bt_app_a2d_data_cb(uint8_t *data, int32_t len) // Обратный вызов, передача данных в CBS media
{
    if (data == NULL || len < 0) { return 0; }
    uint16_t *p_buf = (uint16_t *)data;
    int i=0;
  
    if(flag_data==0) // Выбрать буфер для передачи
    {
        for (; i < (len >> 1); i++) // Один вызов забирает 256 слов uint16_t
        {
            p_buf[i]=SPI_BUF_1[cnt_send];
            cnt_send++; 
        }
        if(cnt_send==1024)  {  flag_data=1; cnt_send=0; flag_buf_Ready_1=0;  }// Если буфер №1 полностью передан, переключить флаг на буфер №2.
    } 
    else 
    {
        for (; i < (len >> 1); i++)  // Один вызов забирает 256 слов uint16_t
        {
            p_buf[i]=SPI_BUF_2[cnt_send];
            cnt_send++;
        }
        if(cnt_send==1024)  {  flag_data=0; cnt_send=0;  flag_buf_Ready_2=0;  } Если буфер №2 полностью передан, переключить флаг на буфер №1.
    }
    return len;
}



static void SPI_RX_DATA_Task(void* arg)
{
    /*
    Инициализация шины SPI из примера "SPI Slave / receiver"
    */
    flag_buf_Ready_1=0; // buffer №1 ready flag 
    flag_buf_Ready_2=0; // buffer №2 ready flag 
    flag_data=0;     // Переключение флагов между буферами
    t.length = 2048*8;     //  2048*8 bit
    t.tx_buffer = NULL;

    for(;;) 
    {       
        if((flag_buf_Ready_1==0)&&(flag_buf_Ready_2==0))
        {  
            ESP_LOGI(TAG, "Buffer empty"); // Проверка своевременности поступления данных.
        }   


        if((flag_buf_Ready_1==0) || (flag_buf_Ready_2==0))  // Если один из буферов полностью отправлен в CBS
        {
            if(flag_buf==0)   { t.rx_buffer = SPI_BUF_1; }  // Выбрать буфер для новых данных
            else { t.rx_buffer = SPI_BUF_2; }


            gpio_set_level(GPIO_RX_Ready, 1); // Сигнал для STM32, мне нужны новые данные.
            spi_slave_transmit(RCV_HOST, &t, portMAX_DELAY);
            gpio_set_level(GPIO_RX_Ready, 0); // Транзакция завершена, и новые данные больше не нужны.  


            if(flag_buf==0) 
            {   
                flag_buf_Ready_1 = 1; 
                flag_buf=1;
            } // Установить флаг готовности буфера №1
            else            
            { 
                flag_buf_Ready_2 = 1; 
                flag_buf=0; 
            } // Установить флаг готовности буфера №2
        }     
    } // for
} // SPI_RX_DATA



static void Bt_Init_Task(void *arg)
{
    /*
    Задача заполнена примером "A2DP Source".
    Запрашивает выделение памяти и инициализацию Bluetooth....
    Я ​​удалил программный таймер "heart_beat".
    После инициализации я сразу подключаюсь к целевому устройству.
    */
    for(;;) { vTaskDelay( 1 / portTICK_PERIOD_MS ); }
}


// --------------------     All code main()
void app_main(void)
{  
    xTaskCreatePinnedToCore(Bt_Init_Task, "Bt_Init_Task", 6144, NULL, 10, &Bt_Init_Task_handle, 0);
    xTaskCreatePinnedToCore(SPI_RX_DATA_Task, "SPI_RX_DATA", 4096, NULL, 20, &SPI_RX_Task, 1); 
 }
Цитата
Создатель темы Размещено : 19.03.2025 11:35
Aveal
(@aveal)
Top level Admin

Добрый день,

первая мысль - прологировать все процессы:

  • перед началом транзакции по SPI
  • по окончанию транзакции по SPI
  • внутри bt_app_a2d_data_cb() для разных веток if(flag_data==0)

И во всех этих точках вывести значения flag_data, flag_buf_Ready_1, flag_buf_Ready_2, flag_buf, cnt_send.

ОтветитьЦитата
Размещено : 19.03.2025 12:29
(@13radeon)
Level 1

@aveal 

Буферы сменяются и заполняются как положен, я проверял. Но сейчас я выяснил, следующее:

Я объявил статичный массив на 11776 элементов и запустил его воспроизведение в цикле. В массив я поместил фрагмент своего фала WAV. По нажатию кнопки я активировал / деактивировал транзакции SPI. Выяснил что транзакции на звук не оказывают ни какого эффекта.... Похоже проблема с доступом к этим буферным массивам или по каким то причинам таки не хватает скорости SPI. Я так же выяснил что одна транзакция дорого обходится по времени в плане работы DMA... Что бы это компенсировать, нужно увеличить объем данных за одну транзакцию. Но если я не путаю, то транзакция более 2048*8 бит вызывала крах ОСи... Буду еще пробовать 

ОтветитьЦитата
Создатель темы Размещено : 19.03.2025 16:33
(@13radeon)
Level 1

@aveal И так, я победил своих демонов! 

1. В документации странным образом описан SPI в slave при использовании DMA. Указано что в таких условиях SPI должен работать в mode 1 или 3. Хотя прекрасно заработал на мод 0, или я что то не так понял.

2. В той же ветке есть табличка, которая гласит, что SPI нужно юзать < 11 МГц, в итоге полетел на 21, но на 40 уже не смог.

3. С STM32 работаю в Keil. Данные засылал ручками в цикле загоняя данные напрямую в регистр вроде - SPI1->DR = DAT[i]. Разумеется ожидания готовности были соблюдены. Такой же метод прекрасно работает с st7789 загоняя весь буфер целиком и все в порядке. Но тут от безысходности я решил подключить логический анализатор. На угад тыкая в поля данных наткнул на странную вещь: Иногда с STM проскакивало не 8 бит клока, а 9... За тем несколько транзакций по 8 и затем снова 9... 

Короче говоря, вызвал HAL_SPI и с его помощью все заработало. Пока не понял что за прикол.... 

Использовать метод SPI1->DR = DAT[i] я начал потому, что на дисплей 240*320 это давало хороший буст скорости и проблем не было. Что тут, хз.

4. Размер буфера должен быть значительно больше. Дело в том что, не смотря на маленький объем который требуется за раз в колбеке bt_app_a2d_data_cb() - (256 байт uint16), кодак вызывает колбек на забор данных несколько раз подряд, а за тем может не обращаться к нему некоторое продолжительное время. 

Таким образом я пришел к буферу размером uint16_t SPI_Buf[10240]; 5120 уже не хватает. Промежуточные минимальные не искал пока.

Экспериментальные коды для STM32 и ESP32 прикрепил ниже.

 

ОтветитьЦитата
Создатель темы Размещено : 21.03.2025 21:20
Aveal
(@aveal)
Top level Admin

Отлично, по факту довольно быстро удалось найти достаточно неочевидную проблему )

ОтветитьЦитата
Размещено : 22.03.2025 12:13
(@13radeon)
Level 1

Эта копия для ESP лучше той, что я прикрепил ранее.
Я заблокировал возможность одновременного доступа к одной и той же области буфера для Spi_RX_Data_Task и bt_app_a2d_data_cb. Это более правильно и удобно для отладки.
Я также удалил все лишнее и добавил комментарии...
Обратите внимание, что я также избавился от файла bt_app_core.c. Я переместил несколько его функций в файл main.c.
Имейте это в виду, если решите запустить его с примером Espressif "A2DP source".
Удачи!

 

ОтветитьЦитата
Создатель темы Размещено : 22.03.2025 14:03
(@13radeon)
Level 1

@aveal В общей сложности я потратил две недели что бы запустить ESP32 c A2DP.... Это было не так просто))) Хотя стоило ожидать. Я ESP32 купил давно, а приторонулся впервые к ней две недели назад. Собственно у каждой новой железяки есть свои загоны.

Так например у ESP какого то реха максимальная длина одной транзакции ограничена не 4096 байт, как одна из степени двойки, а то ли 4092, то ли 4093. То есть где с SD карты ты считываешь блоками например по 512 или кратно этому, а иначе нельзя, ESP не умеет работать с максимальной транзакцией 4096. Espressif, вы там че курили?! Откуда вылезла цифра 4092?! 😄 С картой памяти лучше всего работать большими блоками а не 512. Короче я думаю что собрал все грабли которые только можно было собрать.

 
 
ОтветитьЦитата
Создатель темы Размещено : 22.03.2025 14:09
(@13radeon)
Level 1

PS:

Обратите внимание, что для отправки данных в ESP вам необходимо поменять местами старший и младший байты на стороне хоста или добавить эту функцию в мой пример!!! Иначе вы получите белый шум, а не музыку!

ОтветитьЦитата
Создатель темы Размещено : 22.03.2025 14:40
Aveal
(@aveal)
Top level Admin

Запись от: @13radeon

Короче я думаю что собрал все грабли которые только можно было собрать.

😆

ОтветитьЦитата
Размещено : 23.03.2025 11:35
(@13radeon)
Level 1

Грабли номер "Много".

Не смотря на то что в документе написано, что максимальная скорость SPI в slave около 11МГц, выясняется что только на прием легко летит и на 18Мгц. А вот если ты хочешь фул дуплекс, то получай дупло! На 9МГц возвращает мусор. На 4.5 уже норм. Где нижний предел хрен знает 😐 

ОтветитьЦитата
Создатель темы Размещено : 24.03.2025 17:59
Поделиться: