¿Por qué no usar siempre DMA a favor de las interrupciones con UART en STM32? [cerrado]

Pasé mucho tiempo el mes pasado haciendo que UART (para MIDI) funcione con un STM (STM32F103C8T6) usando interrupciones, sin mucho éxito.

Sin embargo, esta noche usando DMA funcionó bastante rápido.

Dado que, por lo que he leído, DMA es más rápido y alivia la CPU, ¿por qué no usar siempre DMA a favor de las interrupciones? Sobre todo porque en el STM32 parece haber bastantes problemas.

Estoy usando STM32CubeMx/HAL.

No todos los µC obtuvieron DMA. Si puedes usarlo , entonces genial, úsalo si necesitas la velocidad.
@HarrySvensson No sé si realmente necesito la velocidad, pero DMA lo hice funcionar en unas pocas horas, mientras probé interrupciones durante varias semanas (tiempo de pasatiempo libre). Pensé que sería mejor intentar primero directamente (eso funcionó), luego interrumpir (no funcionó bien), que DMA (usar interrupciones).
¿Por qué no? O bien es una cuestión de opinión, una que busca adivinar cuál es la posible razón técnica, o de la misma manera es demasiado amplia y, por lo tanto, no es una cuestión que pertenezca aquí. Para nombrar un ejemplo aleatorio, DMA significará una mayor latencia al reclamar los datos, especialmente porque no obtiene ningún beneficio real a menos que permita que recopile varios caracteres. A menudo eso puede estar bien, a veces puede que no.
Si lograr que las interrupciones funcionaran tomó semanas, es porque abordó la tarea de manera incorrecta; hacer que DMA funcione bien podría llevar más tiempo; en realidad, es una tarea más compleja, por lo que la aparente facilidad de la tarea más compleja sobre la más simple probablemente se deba a los recursos que usó como guía con cada una, no al mecanismo en sí.
@Michel Keijzers: nunca me ha dicho la tasa de bits de su aplicación, o me la he perdido. Estoy bastante sorprendido de que las interrupciones no te hayan funcionado. A 72 MHz y 115200 baudios, tiene la friolera de 5000 relojes por carácter.
Nunca asuma que dma libera la cpu, a veces sí, la cpu sigue funcionando, a veces no, el procesador está congelado para contener el bus para el motor dma. Trivial hacer esto con una implementación de brazo, así que no puedo decir que todos los brazos son de esta manera y todos los x86 son de esa manera o lo que sea, no es tan simple, siempre debe examinar el diseño del sistema y tal vez hacer un poco de piratería. El chip que tiene puede muy bien liberar el núcleo del brazo, esto es solo un comentario sobre dma. En cuanto a su pregunta, no tiene sentido que no pueda mantenerse al día y dma + int es probablemente la solución completa si no puede simplemente sondear.
dado que está utilizando una HAL y/o cualquier biblioteca proporcionada por el proveedor, es posible que el problema no esté en el chip sino en la HAL o la biblioteca proporcionada. ¿Examinó cuidadosamente cada centímetro de la biblioteca para asegurarse de que el problema no estaba en el código o en una combinación de su código, el de ellos y el compilador?
Tenga en cuenta que MIDI es de 31250 baudios, por lo que es probable que el brazo no esté sudando en absoluto al lidiar con esto.
@old_timer ... ni siquiera si recibo 1 byte a la vez (¿que la configuración de la nueva interrupción DMA lleva demasiado tiempo?)
Las interrupciones son bastante triviales en el puerto serie STM32F. ¿Por qué no publica una pregunta con su código para que algunos de nosotros podamos tratar de detectar dónde se está equivocando? Nunca es una buena idea piratear el código hasta que funcione sin comprender cuál era el problema subyacente.
@Jon Completamente cierto... Lo haré esta noche (no estoy en casa ahora). Bueno, considerando las muchas publicaciones sobre UART con interrupciones (no DMA) en STM, no es realmente trivial.
En mi (no tan) humilde opinión, esta es una de las desventajas de usar el horrible e hinchado Cubo. Escriba el software desde cero, aprenderá exactamente cómo funciona el UART (porque tiene que hacerlo), comprenderá mucho mejor el periférico y, a la larga, le ahorrará mucho tiempo.
@Jon ... tal vez espere un poco ... primero quiero tener una versión que funcione con DMA, luego volveré a las interrupciones para ver si funciona o no ... luego haré la pregunta (I tengo tiempo muy limitado lo siento).
@DiBosco cierto ... sin embargo, primero continuaré con algunas verificaciones con el DMA (ya que parece funcionar), luego volveré a las interrupciones, y probablemente usaré la forma de 'nivel bajo'.

Respuestas (6)

Si bien DMA alivia la CPU y, por lo tanto, puede reducir la latencia de otras aplicaciones impulsadas por interrupciones que se ejecutan en el mismo núcleo, existen costos asociados con esto:

  • Solo hay una cantidad limitada de canales DMA y existen limitaciones sobre cómo esos canales pueden interactuar con los diferentes periféricos. Otro periférico en el mismo canal puede ser más adecuado para el uso de DMA.

    Por ejemplo, si tiene una transferencia I2C masiva cada 5 ms, parece un mejor candidato para DMA que un comando de depuración ocasional que llega a UART2.

  • Configurar y mantener DMA es un costo en sí mismo. (Normalmente, la configuración de DMA se considera más compleja que la configuración normal de la transferencia impulsada por interrupciones por carácter, debido a la administración de memoria, más periféricos involucrados, DMA que usa interrupciones y la posibilidad de que necesite analizar los primeros caracteres fuera de DMA de todos modos, ver más abajo.)

  • DMA puede usar energía adicional , ya que es otro dominio del núcleo que necesita ser sincronizado. Por otro lado, puede suspender la CPU mientras la transferencia DMA está en curso, si el núcleo lo admite.

  • DMA requiere búferes de memoria para funcionar (a menos que esté haciendo DMA de periférico a periférico), por lo que hay un costo de memoria asociado.

    (El costo de la memoria también puede estar ahí cuando se usan interrupciones por carácter, pero también puede ser mucho más pequeño o desaparecer si los mensajes se interpretan de inmediato dentro de la interrupción).

  • DMA produce una latencia porque la CPU solo recibe una notificación cuando la transferencia está completa o medio completa (consulte las otras respuestas).

  • Excepto cuando transmite datos hacia/desde un búfer de anillo, necesita saber de antemano cuántos datos recibirá/enviará.

    • Esto puede significar que es necesario procesar los primeros caracteres de un mensaje utilizando interrupciones por carácter: por ejemplo, al interactuar con un XBee, primero leería el tipo y el tamaño del paquete y luego activaría una transferencia DMA en un búfer asignado.

    • Para otros protocolos, esto puede no ser posible en absoluto, si solo usan delimitadores de fin de mensaje: por ejemplo, protocolos basados ​​en texto que usan '\n'como delimitador. (A menos que el periférico DMA admita la coincidencia en un carácter).

Como puede ver, hay muchas compensaciones a considerar aquí. Algunos están relacionados con limitaciones de hardware (número de canales, conflictos con otros periféricos, coincidencia de caracteres), algunos se basan en el protocolo utilizado (delimitadores, longitud conocida, búferes de memoria).

Para agregar alguna evidencia anecdótica, me enfrenté a todas estas compensaciones en un proyecto de pasatiempo que usó muchos periféricos diferentes con protocolos muy diferentes. Había que hacer algunas concesiones, principalmente basadas en la pregunta "¿cuántos datos estoy transfiriendo y con qué frecuencia voy a hacerlo?". Básicamente, esto le brinda una estimación aproximada del impacto de la transferencia simple impulsada por interrupciones en la CPU. Por lo tanto, le di prioridad a la transferencia I2C antes mencionada cada 5 ms sobre la transferencia UART cada pocos segundos que usaba el mismo canal DMA. Otra transferencia UART que ocurre con más frecuencia y con más datos, por otro lado, tiene prioridad sobre otra transferencia I2C que ocurre con menos frecuencia. Todo son compensaciones.

Por supuesto, usar DMA también tiene ventajas, pero eso no es lo que pediste.

Gracias por tu respuesta detallada. MIDI será la parte más crítica, así que supongo que DMA es adecuado para ello (aunque la velocidad es baja: 31250 baudios). Tengo suficientes canales DMA, luego voy a usar otro STM32 cuando use 4 USART. No necesito suspender la CPU, ya que tendrá alimentación USB de 5 V, y necesito procesar entre los mensajes (para procesar los mensajes en el bucle principal). Tengo una lectura de 256 bytes y un búfer de transmisión de 256 bytes. Puedo aumentarlo más tarde si es necesario. El STM32f103c8t6 tiene 20 KB de RAM, el eventual STM que usaré tiene 192 KB.
Y me das una muy buena idea de cómo mejorar. Hasta ahora siempre leo 1 byte y compruebo continuamente cuando se recibe un mensaje completo (MIDI). Pero puedo leer el primer byte, y dependiendo de eso, se conoce principalmente el tamaño y puedo preguntar por el resto. Esto me costó otro pequeño búfer, pero está bien.
La lectura de bytes individuales con DMA es muy ineficiente. Para una latencia más baja y una mayor eficiencia, sería conveniente usar interrupciones por carácter hasta que sepa el tamaño y luego cambiar a DMA.
Bueno, tuve muchos problemas al usar interrupciones (sin DMA), creo que usaré una recepción de DMA de 1 byte, y luego sé cuántos bytes esperaré y haré una solicitud de DMA para obtener más.
Probablemente sea un error: debe corregir su código de interrupción simple, sin DMA.

El uso de DMA generalmente significa que ya no está tomando una interrupción en cada carácter, sino solo después de que se haya recibido (o transmitido) un "búfer lleno" de caracteres. Esto aumenta la latencia del procesamiento de esos caracteres: el primer carácter no se procesa hasta que se recibe el último carácter en el búfer.

Esta latencia puede ser algo malo, especialmente en una aplicación sensible a la latencia como MIDI, donde unos pocos ms aquí y allá pueden sumar serios problemas de reproducción para presentaciones en vivo.

Lo que hago es recibir 1 byte a la vez (por lo tanto, un búfer 'DMA' de 1 byte) y después de cada devolución de llamada DMA de ese byte, para almacenarlo en un búfer de anillo que manejo manualmente. En mi ciclo principal, tengo la intención de verificar los mensajes MIDI completos y procesarlos.
DMA se usa normalmente para obtener varios bytes y solo se interrumpe cuando se han recibido todos. La interrupción después de un solo byte es normal cuando no se usa DMA, por lo que me hace preguntarme: ¿cuál es el punto de la complicación adicional de usar DMA para eso?
@MichelKeijzers Entonces, lo que hace es prácticamente lo mismo que haría en implementaciones puras impulsadas por interrupciones. Por lo tanto, no hay ningún beneficio en el uso de DMA en este caso y su problema original probablemente no se resuelva con el DMA sino con la reescritura de su código (ISR, configuración).
@JimmyB ... gracias ... sin embargo, debido a la respuesta de Jonas a continuación, haré una mejora para leer tantos bytes ya que el mensaje es largo. Sé esto después de recibir el primer byte (en la mayoría de los casos). De lo que se beneficiará más usar DMA sobre interrupciones.

DMA no es un sustituto de las interrupciones, ¡generalmente se usan juntas! Si está utilizando DMA para enviar datos a través de un UART, por ejemplo, todavía necesita una interrupción para saber cuándo se completa el envío.

Es cierto, tal vez solo en el STM32, el mecanismo de interrupción (sin DMA puro) es un poco torpe en comparación con DMA directo.
@duskwuff No realmente; puede sondear para ver cuándo finaliza el DMA, y es posible que desee hacerlo porque una de las razones clave para usar DMA es no tener que preocuparse por el puerto serie hasta que su programa esté en un estado en el que pueda actuar sobre el recibido. datos. O para DMA saliente, simplemente puede sondear para ver si es posible agregar más al búfer de envío.
@MichelKeijzers: IDK el chip específico, pero generalmente la alternativa a DMA no es literalmente interrupciones, es E/S programada (donde usa instrucciones de CPU para leer/escribir datos desde/hacia un registro de E/S). En un controlador de interrupciones, normalmente haría una lectura, y luego tal vez otra en caso de que un personaje entrara mientras estaba leyendo la primera, especialmente si eso no activará otra interrupción. O lea hasta que un búfer interno esté vacío si existe tal búfer. Obviamente, necesita más interrupciones para PIO y configurarlas de manera diferente.
@ChrisStratton Buen punto ... hasta ahora no he verificado si es posible transmitir, solo transmito algo, sin verificar si está bien. Probablemente si no, lo intente de nuevo más tarde.
@PeterCordes Parece que el STM32 tiene suficientes interrupciones para DMA y leo cada vez solo 1 byte. Incluso el STM32 (F103c8t6) más simple tiene suficientes puertos/interrupciones DMA disponibles.
Leer un byte con DMA tiene un mérito limitado; probablemente reservado para el caso en que el periférico no tiene registro de retención (a diferencia de los MCU más modernos) y, por lo tanto, el byte recibido debe extraerse de inmediato para dejar espacio para el resto, pero el procesador por alguna razón no puede atender una interrupción. Tenga en cuenta que DMA aún puede distorsionar la temporización del programa principal, ya que fuerza el arbitraje del bus si la CPU también necesita acceder a la RAM o al mismo bus periférico, aunque no tanto como lo haría el servicio de una interrupción.

El uso de DMA presenta algunas preguntas y desafíos interesantes más allá de todas las demás consideraciones del uso de periféricos UART. Le daré algunos ejemplos: suponga que su uC está sentado en un bus RS485 (o lo que sea) con otros dispositivos. Hay muchos mensajes en el bus, algunos están destinados a su uC, otros no. Además, suponga que todos estos vecinos de bus hablan un protocolo de datos diferente, lo que implica que las longitudes de los mensajes son diferentes.

Algunas preguntas que solo surgen cuando se usa DMA son:

  • ¿cuándo interrumpo?
    • A los DMA solo les gusta interrumpir cuando han transferido una cantidad preestablecida de datos.
    • ¿Qué hace si nunca recibe suficientes datos para activar una interrupción de DMA?
  • ¿Qué sucede si solo recibe un mensaje parcial cuando se interrumpe el DMA?
  • ¿Cómo son sus búferes RX? ¿Son lineales o circulares?
    • DMA puede ser un participante de búfer circular ingobernable en el sentido de que solo obedece el límite de la dirección, pero no tiene problemas para superar los otros punteros en el sistema de búfer circular.

De todos modos, solo alimento para el pensamiento.

Gracias por esas consideraciones. Actualmente, siempre recibo 1 byte y lo almaceno en un búfer de anillo, ya que, de hecho, mis mensajes (MIDI) pueden tener diferentes longitudes y no sé cuál recibiré a continuación. En mi bucle principal, compruebo los mensajes completos para procesarlos (y si están completos, los elimino del búfer circular). Por lo tanto, siempre recibiré suficientes datos (a menos que pierda bytes, tengo que verificar eso). Mi búfer RX tiene solo 1 byte, pero lo copio en un búfer de anillo/circular. No verifiqué si está lleno (necesito agregarlo).
Oye, no te preocupes. Estoy seguro de que su aplicación estará bien programada. Como otros mencionaron, DMA es genial, pero no es gratis, eso es todo. Introduce consideraciones adicionales en el sistema que no existen si puede salirse con la suya sin usarlo.
Bueno, espero, todavía soy un principiante.

En el lado de recepción (según recuerdo), DMA termina en una coincidencia de caracteres o en el recuento de terminales. Algunos protocolos y muchas aplicaciones interactivas no encajan fácilmente en este modelo y realmente necesita manejar las cosas carácter por carácter. Las técnicas de DMA también pueden ser frágiles si el enlace de comunicaciones no es confiable, perder un solo carácter en la transmisión puede estropear fácilmente su máquina de estado de DMA.

De hecho, recibo byte por byte y lo copio manualmente en un búfer de anillo para procesarlo más tarde.

He usado el STM32CubeMx/HAL en un par de proyectos ahora y descubrí que el software de manejo de UART que genera tiene deficiencias definidas en el lado de recepción.

Al transmitir, normalmente querrá enviar un bloque de datos o una línea de texto. En este caso, usted sabe de antemano cuánto dura la transferencia de datos, por lo que usar el DMA es una solución obvia. Obtiene una interrupción una vez que se completa la transferencia y puede usar la función de devolución de llamada completa de UART TX para indicar a su código principal que la transmisión está completa y puede enviar otro bloque de datos.

Cuando se trata de la recepción de datos, todas las funciones proporcionadas por ST asumen que usted sabe cuántos caracteres le dará el dispositivo de envío antes de que comience a enviar. Normalmente esto no se sabe. La funcionalidad de interrupción coloca los datos recibidos en un búfer y solo indica que hay datos disponibles cuando se ha recibido el número predefinido de caracteres. Si intenta utilizar la función DMA o de interrupción para recibir datos mediante la configuración de transferencias secuenciales de un solo carácter, el tiempo de configuración para cada uno de estos significará que perderá caracteres a cualquier velocidad que no sea la de datos más lenta (la velocidad en baudios que empezar a perder datos dependerá de la velocidad de reloj de su procesador) y cargará el procesador en exceso, sin dejar ciclos de instrucción para ningún otro procesamiento

Para evitar esto, he escrito mi propia función de controlador de interrupciones que almacena los datos en un pequeño búfer circular local y establece un conteo que lee el código principal (un semáforo de conteo RTOS) para indicar que hay datos recibidos listos. Luego, el código principal puede recopilar los datos de este búfer cuando lo desee, sin importar si hay algún retraso en la recopilación de datos, siempre que el búfer local no se desborde antes de que se recopilen los datos.

Yo hago exactamente lo mismo (creo). Leo 1 byte a la vez y lo almaceno en un búfer cíclico, y tengo la intención de verificar en el ciclo principal los mensajes completos. Aunque se puede mejorar un poco.
¿Crees que podría encontrarme con el problema de que configurar el DMA cada vez sobrecargará mi procesador/faltarán caracteres a 31,250 baudios?
Siempre que configure el DMA para transferir una cantidad de caracteres a la vez, esto no será un problema. Tengo 4 UART que ejecutan 115200 y superior e I2C usando DMA sin problemas. Las transmisiones UART son todas ~20 bytes o más. El problema estaba usando DMA para recibir en el UART (procesador L4 a 80 MHz, 9600 baudios).
Actualmente lo configuro en 1 byte a la vez, pero puedo mejorarlo (haciendo el primer byte y luego verificando cuántos bytes adicionales se necesitan).