Добрый день! Поставил задачу передать музыку на 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 уже норм. Где нижний предел хрен знает 😐