Добрый день! Поставил задачу передать музыку на 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); }
Добрый день,
первая мысль - прологировать все процессы:
- перед началом транзакции по 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.
Буферы сменяются и заполняются как положен, я проверял. Но сейчас я выяснил, следующее:
Я объявил статичный массив на 11776 элементов и запустил его воспроизведение в цикле. В массив я поместил фрагмент своего фала WAV. По нажатию кнопки я активировал / деактивировал транзакции SPI. Выяснил что транзакции на звук не оказывают ни какого эффекта.... Похоже проблема с доступом к этим буферным массивам или по каким то причинам таки не хватает скорости SPI. Я так же выяснил что одна транзакция дорого обходится по времени в плане работы DMA... Что бы это компенсировать, нужно увеличить объем данных за одну транзакцию. Но если я не путаю, то транзакция более 2048*8 бит вызывала крах ОСи... Буду еще пробовать
@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 прикрепил ниже.
Отлично, по факту довольно быстро удалось найти достаточно неочевидную проблему )
Эта копия для ESP лучше той, что я прикрепил ранее.
Я заблокировал возможность одновременного доступа к одной и той же области буфера для Spi_RX_Data_Task и bt_app_a2d_data_cb. Это более правильно и удобно для отладки.
Я также удалил все лишнее и добавил комментарии...
Обратите внимание, что я также избавился от файла bt_app_core.c. Я переместил несколько его функций в файл main.c.
Имейте это в виду, если решите запустить его с примером Espressif "A2DP source".
Удачи!
@aveal В общей сложности я потратил две недели что бы запустить ESP32 c A2DP.... Это было не так просто))) Хотя стоило ожидать. Я ESP32 купил давно, а приторонулся впервые к ней две недели назад. Собственно у каждой новой железяки есть свои загоны.
Так например у ESP какого то реха максимальная длина одной транзакции ограничена не 4096 байт, как одна из степени двойки, а то ли 4092, то ли 4093. То есть где с SD карты ты считываешь блоками например по 512 или кратно этому, а иначе нельзя, ESP не умеет работать с максимальной транзакцией 4096. Espressif, вы там че курили?! Откуда вылезла цифра 4092?! С картой памяти лучше всего работать большими блоками а не 512. Короче я думаю что собрал все грабли которые только можно было собрать.
PS:
Обратите внимание, что для отправки данных в ESP вам необходимо поменять местами старший и младший байты на стороне хоста или добавить эту функцию в мой пример!!! Иначе вы получите белый шум, а не музыку!
Короче я думаю что собрал все грабли которые только можно было собрать.
😆
Грабли номер "Много".
Не смотря на то что в документе написано, что максимальная скорость SPI в slave около 11МГц, выясняется что только на прием легко летит и на 18Мгц. А вот если ты хочешь фул дуплекс, то получай дупло! На 9МГц возвращает мусор. На 4.5 уже норм. Где нижний предел хрен знает 😐