¿Cómo configurar DMA para recibir mensajes USART de longitud variable?

Estoy tratando de recibir mensajes USART en un microcontrolador que se origina en una PC que le ordenará ejecutar ciertas pruebas. Estoy usando STM32F4, elegí usar DMA ya que los mensajes en el mismo USART que se originan en el microcontrolador deberían estar a 2000000 bps ya que no pude hacerlo funcionar con una tasa de bits más baja o sin DMA. Estoy usando binario en lugar de ASCII para ambas direcciones.

Lo que he diseñado hasta ahora "funciona" (actualiza casi en tiempo real) siempre que la tasa de mensajes que se envían sea constante, ya que cada mensaje nuevo obliga a que el anterior se elimine del registro DMA. Sin embargo, si se inicia y solo se envía una instrucción/marco USART que resulta ser menor que la cantidad de datos que se recibirán para el mensaje HAL_UART_Receive_DMA(), el mensaje se atasca hasta que el siguiente mensaje lo expulsa.

ingrese la descripción de la imagen aquí

Un escenario aún peor sería si USART_take_sizees 100 y hay un solo marco de 10 bytes en el búfer DMA. En este caso, tendría que esperar otros 9 (10 bytes) marcos antes de recibirlos todos a la vez.

Así es como se ve mi devolución de llamada actualmente.

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    
    //              uart handle, global array, numb of bytes to trigger interrupt       
    HAL_UART_Receive_DMA (huart, raw_serial, USART_take_size);
    
    // function that adds received bytes to the circular buffer 
    AppendSerial(raw_serial,USART_take_size);
            
}

Un subproceso extrae bytes del búfer circular, los recorre y, en función de los bytes SOF y EOF, extrae el mensaje dentro del marco.

Mi problema es que los cuadros que llegan tienen una longitud variada, si USART_take_sizese establece en x y llega un cuadro y su longitud es menor que x, tendrá que sentarse en la matriz de búfer en el hilo de extracción hasta que llegue otro cuadro que hace que aparezcan los bytes necesarios para que el analizador obtenga el mensaje incrustado.

Veo tres formas de resolver esto y cada una no parece correcta:

  1. Establecido USART_take_sizeen 1, que de alguna manera anula todo el punto de DMA

  2. Tener un tamaño de cuadro fijo y rellenar el contenido con ceros antes de EOF si el contenido es menor que el USART_take_size. Esto parece descuidado y derrochador. También tendría que activarse en cada cuadro, aunque la tasa general en esa instancia podría ser mucho más alta.

  3. Configure el DMA para activar ISR en un número variable de bytes recibidos, siempre que haya habido una cierta cantidad de inactividad anterior.

No sé mucho sobre DMA, y si bien parece ser una compensación entre cuántos ISR se activan y qué mensaje mínimo puede leer, creo que debería haber una forma de recibir el mensaje después de un período de inactividad. Cualquier otro enfoque de cómo resolver esto sería genial. ¡Gracias!

¿Qué microcontrolador? ¿Hay un FIFO en él? ¿Hay un código de referencia o notas de aplicación de las que pueda basarse?
Usar un STM32 es un problema un poco difícil de resolver. ¿ Realmente necesitas usar DMA?
@ pjc50 es STM32F407, lo hace y el DMA tiene dos registros, uno para la mitad de la devolución de llamada. Estoy recuperando el código principalmente de otros, no pude encontrar un código de AN que se adaptara a mis necesidades.
@Peter Smith, ¿por qué STM32 es más difícil que otros MCU? Necesito recoger mensajes frecuentes, y solo pude enviarlos a la PC con DMA a 2000000. Podría intentar una tasa de bits aún más alta, pero luego usaría incluso más ciclos de reloj si se usa sin DMA y necesito algo de repuesto para otra lógica sirve para impulsar otras periferias que están por venir.
(En respuesta): algunos microcontroladores pueden generar una interrupción del receptor en un 'carácter especial' programado (que puede ser cualquier cosa), pero la serie STM32 no lo hace. En esas situaciones, siempre puedo saber cuándo está completo un mensaje en particular.
@PeterSmith si tuvieran eso significaría que tendría que asegurarme de que ninguno de los mensajes de control tenga ese carácter especial, supongo que no es necesario SOF/EOF, ¿verdad?
@PeterSmith El UART STM32 tiene una interrupción de carácter. Lo uso exactamente para este propósito junto con DMA. Se llama coincidencia de caracteres y comparte registros con coincidencia de direcciones.

Respuestas (2)

Tuve un problema similar en uno de mis proyectos. Lo resolví creando un búfer lo suficientemente largo para almacenar el mensaje más largo posible y usando DMA para transferir desde USART. Para detectar el final del mensaje, utilicé la interrupción de detección de línea inactiva que se activa cuando el receptor USART deja de recibir datos.

Lo mismo aquí, la detección de línea IDLE funciona de maravilla con DMA. Tuve que agregar soporte para esto en la biblioteca STM HAL. Una vez que se activa la interrupción IDLE, deshabilito RX DMA y lo vuelvo a habilitar con un búfer diferente.

El STM32 DMA no puede hacer lo que usted quiere por sí solo. Para manejar mensajes de longitud variable, tendría que ser capaz de analizar el mensaje de forma inteligente y determinar la longitud utilizando cualquier esquema de codificación que haya decidido. Esto está mucho más allá de sus capacidades.

Realmente solo puedo pensar en dos opciones para su problema:

  1. Solo procesa cada byte en el USART recibe ISR. El STM32 tiene una sobrecarga de interrupción bastante baja. Si su aplicación puede manejarlo, a veces es mejor ir con lo simple, aunque no sea tan elegante.

  2. Puede configurar fácilmente el DMA para que funcione como un búfer FIFO. El DMA cargará bytes en el búfer y luego configurará una interrupción de temporizador periódica para verificar el búfer en busca de datos y analizar mensajes. Este es un enfoque intermedio en comparación con la opción 1, ya que le permite reducir la sobrecarga de interrupciones a expensas de la latencia. Puede ajustar el período de sondeo del temporizador con respecto al tamaño fifo para equilibrar sus necesidades particulares de procesamiento en tiempo real. También hay trucos que puede hacer con los disparadores de nivel de DMA, aunque la utilidad de esto dependerá del formato de su mensaje.

La #2 es probablemente la mejor opción. Ni siquiera tendría que usar un temporizador. Uno podría simplemente dejar que DMA FIFO recopile los datos y sondee sincrónicamente el búfer FIFO en algún punto durante el flujo del programa donde sea apropiado. O bien, uno podría iniciar el temporizador solo al recibir el primer carácter, luego drenar el búfer y detener el temporizador cuando el temporizador expire.
@JimmyB Lo más probable es que drenar el búfer sea solo marcar que ahora se puede volver a escribir, posiblemente reiniciando el DMA o moviendo un índice de lectura/escritura al principio. Sondeo sincrónicamente un indicador para analizar establecido por DMA completo o interrupciones de coincidencia de caracteres para saber cuándo se recibe un mensaje completo. Los temporizadores son demasiado valiosos para desperdiciarlos.
@Toor Lo que quise decir con "drenar" es "extraer y procesar todos y cada uno de los datos en el búfer", por ejemplo, analizar cualquier mensaje (parcial) ya recibido.