Обычно описанные действия помогают. Может проще использовать DMA в режиме записи по кольцу, организовав тем самым подобие двойного буфера?
В своем проекте использую FreeRTOS. Сделал отдельную задачу на прием информации по УАРТ. В обработчике прерывании от DMA в очередь событий (создаю отдельно) помещаю код возникшего события (буфер заполнен на половину, заполнен полностью, ошибка). В задаче приема вызываю xQueueReceive(..., ..., таймаут_ожидания_окончания_передачи). Если событие принято, обрабатываю данные буфера, запоминаю смещение (первая/вторая половина буфера, зависит от кода события). Если событие не принято (xQueueReceive завершилась по таймауту), определяю количество принятых байт с момента возникновения последнего события (знаю смещение в буфере, знаю сколько еще пришло DMA_GetCurrDataCounter(...)) и снова обрабатываю буфер.
Код
//============================================================================
/// Задача по приему данных от USART_DBG
//=============================================================================
void vDebugReceive(void *pvParameters)
{
const portTickType xTicksToWait = USART_DBG_RX_TIMEOUT_MS / portTICK_RATE_MS;
uint8_t event_code;
uint16_t last_data_cnt = USART_DBG_RX_BUF_SIZE;
uint16_t cnt = 0;
uint8_t offset = 0;
//вечный цикл
for(;;)
{
// ожидаю прихода события (данные считаны либо таймаут)
if( xQueueReceive(dbg_rx_events, &event_code, xTicksToWait) == pdPASS )
{
//получил событие
switch(event_code)
{
case DBG_RX_HALF:
{
// заполнена первая половина приемного буфера
for (; offset < (USART_DBG_RX_BUF_SIZE+1)/2; ++offset)
xQueueSendToBack(dbg_rx_data, (const void*)&dbg_in_buf[offset],0);
last_data_cnt = (USART_DBG_RX_BUF_SIZE+1)/2;
break;
}
case DBG_RX_FULL:
{
// заполнена вторая половина приемного буфера
for (; offset < USART_DBG_RX_BUF_SIZE; ++offset)
xQueueSendToBack(dbg_rx_data, (const void*)&dbg_in_buf[offset],0);
// смещение на начало буфера
offset = 0;
last_data_cnt = USART_DBG_RX_BUF_SIZE;
break;
}
case DBG_RX_ERR:
{
// произошла ошибка при приеме данных. Заново включаем канал DMA
DMA_Cmd(USART_DBG_Rx_DMA_Ch, ENABLE);
offset = 0;
break;
}
}
}
else
{
// проверяю, может пришла часть данных
if ( last_data_cnt != DMA_GetCurrDataCounter(USART_DBG_Rx_DMA_Ch))
{
// т.к. счетчик в DMA декрементируется, то вычитаем из last_data_cnt
cnt = last_data_cnt - DMA_GetCurrDataCounter(USART_DBG_Rx_DMA_Ch);
for (; cnt && offset < USART_DBG_RX_BUF_SIZE; ++offset, --cnt)
xQueueSendToBack(dbg_rx_data,(const void*)&dbg_in_buf[offset],0);
//ОБНУЛЯТЬ offset НЕЛЬЗЯ!!! Должно генерироваться прерывание по заполнению буфера
}
last_data_cnt = DMA_GetCurrDataCounter(USART_DBG_Rx_DMA_Ch);
}
}
}