Reiniciar Rx USART + DMA en STM32L1

Estoy usando un STM32L1 en una placa Nucleo-L152RE. Tengo dispositivos que controlo a través de la ejecución en serie a velocidades de transmisión bastante altas, por lo que estoy tratando de habilitar DMA en el USART. Con el siguiente código, puedo iniciar un Rx DMA, pero el segundo, que empiezo desde el ISR, nunca se completa:

void uart_receive_dma() {

    DMA_InitTypeDef  DMA_InitStructure;

    DMA_DeInit(DMA1_Channel5);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &USART1->DR;
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) buffer;  
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = UART_PACKET_SIZE;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
    DMA_Init(uart_rx_dma_channel[handler->uart_index], &DMA_InitStructure);

    /* RX */
    DMA_ITConfig(DMA1_Channel5, DMA_IT_TC, ENABLE);
    DMA_Cmd(DMA1_Channel5, ENABLE);
    USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
}

void DMA1_Channel5_IRQHandler(void){ 

    DMA_ClearITPendingBit(DMA1_IT_TC5);
    uart_receive_dma();

}

int main(void) {

    RCC_Configuration();
    GPIO_Configuration();
    NVIC_Configuration();
    USART_Configuration();
    /* First Rx, works and MA1_Channel5_IRQHandler gets called
    uart_receive_dma();
    while(1);
}

La primera transferencia DMA funciona bien, por lo que supongo que no estoy borrando algo antes de iniciar la segunda, pero no puedo descifrar qué es.

Dependiendo de algunas condiciones externas, el código real no siempre reinicia la transferencia DMA desde DMA1_Channel5_IRQHandler sino que la reinicia desde otro lugar, por eso no puedo usar DMA en modo circular.

Respuestas (1)

Para iniciar otra transacción DMA, debe programar una duración de transacción. Se puede programar solo cuando un canal DMA está deshabilitado. Entonces, en su caso, el código podría verse así:

void DMA1_Channel5_IRQHandler(void) { 
    DMA_ClearITPendingBit(DMA1_IT_TC5);
    DMA_Cmd(DMA1_Channel5, DISABLE);
    DMA1_Channel5->CNDTR = UART_PACKET_SIZE; // <--- transaction length
    DMA_Cmd(DMA1_Channel5, ENABLE);
}

O puede usar su función uart_receive_dma(), pero debe deshabilitar un canal DMA antes de llamarlo.

void DMA1_Channel5_IRQHandler(void) { 
    DMA_ClearITPendingBit(DMA1_IT_TC5);
    DMA_Cmd(DMA1_Channel5, DISABLE);
    uart_receive_dma();
}

La segunda variante hará lo mismo que la primera, pero llevará mucho más tiempo.