Detección de desbordamiento de DMA en la generación de forma de onda arbitraria

Estoy generando una serie compleja de pulsos en un STM32F103, esencialmente como se describe en la nota de la aplicación ST AN4776 Libro de cocina del temporizador de uso general , sección 5.3. Como resumen rápido, eso significa que estoy usando el modo de ráfaga DMA del temporizador para transferir nuevos valores a ARR, RCR y CCR1 después de cada evento de actualización del temporizador. Se proporciona una descripción más detallada al final de la pregunta, en caso de que no esté familiarizado con la nota de la aplicación.

Mi problema está relacionado con el ancho de banda DMA: los pulsos que genero pueden, en principio, estar espaciados arbitrariamente, incluido solo 1 ciclo de reloj (el temporizador tiene un preescalador de 1). Por supuesto, en este caso, el DMA no puede llegar a tiempo para transferir los datos para el siguiente pulso, y habrá fallas en la salida. Dado que mi aplicación puede tolerar pequeños errores de tiempo infrecuentes, preproceso mis pulsos de modo que el intervalo de pulso más pequeño sea un mínimo fijo (estoy usando 96 tics de reloj en este momento) para darle una oportunidad al DMA. Sin embargo, sigo teniendo fallas que creo que se deben a la DMA, y aumentar el tiempo mínimo incluso a números muy grandes no parece estar ayudando.

La parte clave de la oración anterior es "... Creo que ...". Así que me gustaría encontrar una manera de saber con seguridad si el DMA ha perdido su transferencia o no, preferiblemente algo que pueda dejar en el código siempre ejecutándose, de modo que encuentre fallas muy poco frecuentes.

Lo que he pensado/probado hasta ahora es:

  1. Mirando los registros de error de DMA. Sin embargo, no parece haber un indicador de "transferencia perdida" o algo similar, lo cual tiene sentido ya que la mayoría de las veces no es un error si una transferencia está pendiente por un tiempo. Además, incluso en mi caso no es necesariamente un error si la transferencia está pendiente por un tiempo, así que supongo que esto no me va a ayudar.
  2. Estoy ejecutando esto en dos temporizadores diferentes, TIM8 y TIM1, emitiendo pulsos en dos pines diferentes. Las secuencias de pulsos que estoy preparando siempre tienen una duración total de 0,5 ms (para que pueda reaccionar a eventos externos dentro de 1,5 ms), es decir, hay un pulso inactivo si es necesario, de modo que una de las actualizaciones del temporizador ocurre exactamente en el límite de 0,5 ms . El TIM8 es el maestro, y de hecho cronometro mi bucle principal esperando que la cola DMA esté en el límite de 0,5 ms, es decir, esperando un valor específico del registro DMA CNDTR (lo he comprobado con un osciloscopio, alternando un pin en el bucle principal, que esto funciona y es preciso). Ahora, cuando llegué al momento adecuado según TIM8, sé exactamente dónde debería estar la cola TIM1, por lo que puedo verificar su CNDTR.
  3. En qué estoy trabajando ahora: configuro otro temporizador como referencia y uso su canal de captura para obtener el valor del temporizador de referencia cada vez que se afirma la señal de actualización de TIM8. El valor capturado siempre debe ser exactamente uno de los instantes del pulso y, especialmente, puedo comprobar el último valor capturado en el bucle principal en los límites de 0,5 ms. Usando otro temporizador, puedo hacer la misma verificación en TIM1. La desventaja aquí es que esto todavía no garantiza que las transferencias se hayan completado, ya que los registros en el temporizador principal se actualizan en el orden ARR, RCR, CCR1. Entonces, si ARR se actualizó pero se omitió CCR1, los instantes de pulso en sí mismos serían correctos, aunque las longitudes de pulso de salida no lo serían.

Por supuesto, las fallas que veo podrían deberse a un error en el código que genera los búferes que el DMA envía a los temporizadores. Pero esa es exactamente la razón por la que me gustaría saber con seguridad si al DMA le faltan algunas transferencias o no, para saber si estoy buscando un error en mi código o no. El código en sí pasa un conjunto razonablemente completo de pruebas unitarias, por lo que el error sería sutil si estuviera allí.

Entonces, ¿alguna idea para verificar si mi problema se debe a fallas de DMA o no?

Detalles de lo que estoy haciendo exactamente:

Estoy generando una serie de pulsos, cada uno determinado por la duración de la fase de "encendido" (ancho de pulso) y el tiempo (en tictac del reloj) hasta el siguiente pulso. En términos de estructuras de datos

struct pulse {


     /*
     * These are an image of the timer registers.
     * We don't use repeats but it must be there
     * since the DMA transfers it anyway
     *
     * TODO: support other capture/compare channels than 1
     */


    uint32_t length; //ARR
    uint32_t repeats; //RCR, we don't use this
    uint32_t pulsewidth; //CCR1
};

//Check that pulse is of the correct type for the DMA stream
static_assert(std::is_pod<pulse>::value, "pulse must be POD");
static_assert(sizeof(pulse) == 12, "pulse must not be packed");

pulse pulseArray[MAX_PULSES];

Entonces, el modo ráfaga de TIM8 (y correspondientemente, TIM1) nos permite configurar el siguiente esquema:

  1. El canal DMA del temporizador está programado para moverse desde pulseArrayel registro TIM8_DMAR "Dirección DMA para transferencia completa", en modo circular (por lo que, por supuesto, tengo que seguir llenándolo con nuevos datos de manera circular similar)
  2. el "registro de control DMA" TIM8_DCR se programa con una longitud de ráfaga de 3 y la dirección base TIM8_ARR (consulte la hoja de datos RM0008, página 360-361 para obtener una descripción detallada de estos registros), y la solicitud de actualización DMA se habilita a través del bit 8 de TIM8_DIER
  3. Ahora, en cada actualización del temporizador, debido al modo ráfaga programado anteriormente, el temporizador activa la solicitud DMA 3 veces transfiriendo, en este orden, los campos length, repeatsy pulsewidth, y el modo ráfaga dirige estos a los registros TIM8_ARR, TIM8_RCR y TIM8_CCR1 . Dado que hemos habilitado la precarga tanto en el temporizador (TIM8_CR1 bit 7) como en el canal de comparación (TIM8_CCMR1 bit 3), los datos se transfieren al registro de precarga y se activan en la próxima actualización (es decir, cuando se completa el pulso actual). )

Las Figuras 30 y 33 en la nota de la aplicación son muy esclarecedoras para comprender lo anterior.

Y ahora puedo exponer el problema con un poco más de detalle: suponga lengthque es, por ejemplo, 1. Luego, la cantidad de ciclos de reloj disponibles para que el DMA transfiera el siguiente pulso (que en realidad está 2 posiciones por delante en el búfer debido a la precarga registros, pero eso no es importante aquí) es uno (suponiendo que el preescalador del temporizador es 1, que es en este caso). Obviamente, es imposible que el DMA transfiera 3 palabras de 16 bits en un ciclo de reloj y, por lo tanto, se repiten los valores del ciclo anterior. Llamemos a eso una "falla de DMA", a falta de un término mejor.

Por otro lado, debe haber un mínimo lengthtal que durante cualquier pulso más largo que ese, el DMA tendrá tiempo para transferir todos los datos. Desafortunadamente, este mínimo lengthdepende de los horarios exactos de los autobuses, otro tráfico DMA y sus prioridades, por lo que es realmente difícil determinar esa longitud con lápiz y papel.

Por lo tanto, me gustaría encontrar una manera de detectar, con la mayor certeza posible, que no ha ocurrido una "falla de DMA", para poder ajustar mi mínimo lengthy, al mismo tiempo, asegurarme de que se produzcan otros problemas técnicos. que estoy viendo no se deben a una "falla de DMA".

¿Cómo estás usando DMA en modo ráfaga en un STM32F1? Del libro de recetas del temporizador: En las familias de microcontroladores STM32, hay dos variantes de periféricos DMA: • La función de transferencia de ráfagas DMA admitida solo por los productos STM32F2 Variante de periféricos DMA donde el periférico DMA puede transferir una cantidad configurable de elementos de datos junto a una sola transferencia de datos desencadenar. • En otra variante, como la de los productos STM32F1, la variante de periférico DMA solo admite transferencias individuales; esto significa que solo se transfiere un elemento de datos junto a un activador de transferencia de datos.
@jms el temporizador tiene un modo de ráfaga, consulte RM0008 página 360, o la página equivalente para los temporizadores de propósito general. El libro de cocina es un poco confuso en este punto, recuerdo haber pensado también en algún momento que esto no se puede hacer en la serie F1.
muestre las formas de onda, qué es exactamente lo que desea archivar y qué fallas. Tu descripción es imposible de entender. No te metas con tu objetivo
@ PeterJ_01 Agregué una descripción más detallada al final, ¿espero que eso ayude?
@timo En mi humilde opinión, la respuesta a esto se activará en los detalles de su implementación real. Cómo se ven las estructuras de configuración, si está utilizando o no las bibliotecas precocinadas de ST y qué otras cosas se están ejecutando. ¿Hay alguna forma de publicar el código base completo?
Las transferencias paralelas de DMA deben completarse antes de que el búfer en serie se vacíe como un PISO FIFO, excepto que supongo que pasa por un ciclo a través de escrituras y lecturas de memoria de escaneo de trama intercaladas aleatorias. ¿Parece un conjunto de prueba de datos aleatorios complicado?

Respuestas (2)

Usas DMA en modo circular; ¿Cómo se determina el tiempo de actualización de los registros? Como solo usa 1 búfer, no hay un tiempo tan trivial que no cause fallas. Solo se acercará más a estar libre de fallas al aumentar la precisión del tiempo, pero nunca exactamente libre de fallas en términos de probabilidad.

Hay una función de almacenamiento en búfer doble fácil de usar en los dispositivos stm32f4. Pero en los dispositivos stm32f1, hay elementos para hacerlo manualmente. Tiene los disparadores de interrupción de media transferencia y fin de transferencia allí en el periférico DMA. Diseñe el búfer circular como dos conjuntos de registros, como con cada interrupción, intercambie el puntero del búfer que asumió que DMA lo lea y que la CPU lo actualice.

Cuando tiene un conjunto complejo de problemas de tiempo superpuestos, su mejor opción es configurar un hipervisor, un "jefe de jefes". Está creando un semáforo maestro de 4 fases que repite la cuenta 0-3 sin fin.

Los llamo Fase A, B, C, D. Actúan como habilitadores para que se ejecuten subprocesos específicos. Linux y Android tienen esto incorporado. Lo he usado en LabView y MPLAB. Cualquier proyecto complejo tiene un reloj de 4 fases para cronometrar eventos. Un RTC de facto.

En el peor de los casos, algún fragmento de código tiene que esperar su turno, pero no tiene conflictos cuando se ejecuta.

Etapas:

A. Leer el registro de la ejecución anterior, luego configurar comprobaciones condicionales y preestablecer/restablecer valores cruciales. Ventana abierta para que los ISR se ejecuten solo durante la fase A.

B. Ejecute el código determinista crucial a continuación. Lea cualquier resultado de ISR. Esto determina parte de lo que se ejecuta a continuación, según los ciclos de reloj consumidos y la prioridad. Los controladores de errores se ejecutan primero.

C. Ejecute el código principal en función de los resultados de las fases A y B. Esto incluye reanudar o iniciar los modos de ráfaga DMA, ahora que tiene intervalos de tiempo basados ​​en el tiempo de ejecución de la fase C. Escriba el código para que verifique las banderas de DMA antes de que se agote el tiempo de espera de la fase C.

D. Detener las transferencias DMA. Compruebe si hay errores. Compruebe los temporizadores de 'sombra'. Compruebe si hay sobreejecuciones o si hay tiempo para una ráfaga DMA corta para completar un paquete. Haga CRC y cualquier indicador de error, ahora que tiene un intervalo de tiempo para ellos. Aquí es donde encontraría un error de DMA no coincidente frente a un contador de sombras, etc.

Deje un código numérico o de registro para el estado de aprobación/rechazo y si está bien que la fase B ejecute la ruta normal o un controlador de errores.

¡Gracias por la respuesta! De hecho, esto es en gran medida lo que estoy haciendo en este momento (punto 3 en la primera lista de mi pregunta). Sin embargo, el problema es que, según tengo entendido, no parece haber ningún indicador o del tipo que me diga si el DMA ha fallado o no, que es lo que haría en su punto D. Además, el el proceso es en tiempo real y cualquier error es raro de todos modos, por lo que detenerse para verificar es un poco difícil.