Autor Tema: memcpy - DMA/SPI  (Leído 272 veces)

0 Usuarios y 1 Visitante están viendo este tema.

Desconectado planeta9999

  • Moderadores
  • DsPIC30
  • *****
  • Mensajes: 2736
memcpy - DMA/SPI
« en: 09 de Octubre de 2017, 16:46:45 »
 
hola.

Tengo un problemilla con un desarrollo que captura datos por SPI vinculado a DMA.  En teoría los datos que se reciben por SPI, son 256 bits que se cargan por SPI en un buffer, todo gestionado automáticamente por DMA.

El problema es que al mover el buffer del DMA a una matriz de trabajo, me aparecen dos bits desplazados. La matriz del DMA está definida como de campos numéricos sin signo de 16 bit.

Para mover el buffer del DMA a la matriz de trabajo utilizo memcpy. Aquí está copiando 128 bites del buffer del DMA a una matriz de trabajo, posicionandose a partir del elemento 7 del buffer.
memcpy(wpc_planes[plane][row], plane_buffer+7, ROW_LENGTH * sizeof(uint16_t));

He intentado cambiar la matriz del DMA, para que sea de campos de 8 bit, pero entonces no funciona, no entiendo porque. Esta es la definición de la matriz del DMA original, si la cambio por campos uint8_t no va.
uint16_t plane_buffer[32];  // Buffer del DMA

Y esta es la configuración del DMA para capturar datos por SPI, la cantidad de datos que el DMA transfiere al buffer plane_buffer es de 256, aunque luego de esos solo se usan 128, posicionando la copia con memcpy.
  SPI0_RSER = SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS;
  dmaSPI0rx = new DMAChannel();
  dmaSPI0rx->source((volatile uint16_t&) SPI0_POPR);
  dmaSPI0rx->destinationBuffer(plane_buffer, ROW_LENGTH * sizeof(uint16_t) * 2);
  dmaSPI0rx->triggerAtHardwareEvent(DMAMUX_SOURCE_SPI0_RX);



¿ Se os ocurre como mover datos de manera individual, marcando el bit de inicio, usando memcpy u otra instrucción ?, tal vez usando un puntero con un cast, para acceder a la matriz del buffer a partir de un bit concreto, eso es lo que necesitaría hacer, pero no veo como hacerlo con memcpy.

Entiendo que lo que me está haciendo el memcpy es copiar elementos completos de la matriz, osea que si la matriz es de campos numéricos sin signo de 16 bit, y en el memcpy le sumo +7 al campo de origen que es la matriz del DMA, lo que está haciendo es posicionarme en grupos de 16bit, no veo como puedo decir que copie a partir de un bit concreto del buffer del DMA.
« Última modificación: 09 de Octubre de 2017, 17:07:41 por planeta9999 »

Desconectado KILLERJC

  • Colaborador
  • DsPIC33
  • *****
  • Mensajes: 6050
Re:memcpy - DMA/SPI
« Respuesta #1 en: 09 de Octubre de 2017, 17:33:02 »
No entiendo exactamente cual es el problema.

Citar
marcando el bit de inicio
para acceder a la matriz del buffer a partir de un bit concreto


¿Estas buscando la forma de copiar un dato de forma desalineada? Ejemplo:

Tenes 3 datos de 16bit, y queres copiar lo que esta en rojo?

0x0 xxxx.xxxx.xxxx.xxxx
0x2 xxxx.xxxx.xxxx.xxxx
0x4 xxxx.xxxx.xxxx.xxxx

Respecto a la suma., Supongamos que plane_buffer su direccion es 0x100

 plane_buffer+7 = 0x100 + 0xE  ( 0x100 + 14 )

Es decir el septimo elemento de plane_buffer o plane_buffer[7]

Desconectado planeta9999

  • Moderadores
  • DsPIC30
  • *****
  • Mensajes: 2736
Re:memcpy - DMA/SPI
« Respuesta #2 en: 09 de Octubre de 2017, 20:21:22 »


Si, pero al sumarle 7, no son 7 bits, sino 7 bytes. Y ese es el problema, como se me desplaza 2 bits, no se porque, me interesa copiar del buffer del DMA a partir del 7 byte - 2 bits, para compensar los dos bits que se me desplazan.

Lo que le sumo al buffer al hacer el memcpy, son bytes, no bits. Lo correcto sería averiguar porque se me desplaza 2 bits, creo que ya lo se, mañana lo probaré, cuando salto a la rutina que hace el memcpy, lo hago con una interrupción a un pulso de sincronismo, pero hay otros pulsos de sincronismo, creo que no estoy cogiendo el correcto y por eso pierdo bits.


Desconectado KILLERJC

  • Colaborador
  • DsPIC33
  • *****
  • Mensajes: 6050
Re:memcpy - DMA/SPI
« Respuesta #3 en: 09 de Octubre de 2017, 20:29:44 »
El memcpy no va a causarte ese offset de 2 bits, eso es directamente un problema o de lo que llega al SPI o el DMA ( aunque no veo como el DMA puede rotarte 2 bits ).

Para sumarle 7 bytes y no 14 bytes ( 7 * tamaño ) vas a tener que castear el puntero a un uint8_t y ahi si sumarle 7. Asi tenes 7 bytes de offset.

Y la otra es que esto deberia ser un puntero:

Citar
memcpy(wpc_planes[plane][row], plane_buffer+7, ROW_LENGTH * sizeof(uint16_t));

Le faltaria el &, a no ser que sea wpc_planes[plane][row][algomas] y pienso que estaria bien si fuera asi.

Desconectado planeta9999

  • Moderadores
  • DsPIC30
  • *****
  • Mensajes: 2736
Re:memcpy - DMA/SPI
« Respuesta #4 en: 09 de Octubre de 2017, 20:32:59 »


Segurame el problema es que la señal que uso como interrupción para saltar a la función que va a hacer el memcpy no es la adecuada.

En esta captura del analizador lógico, está en amarillo el pulso (RISING) vinculado a la interrupción que salta a la rutina donde se hace el memcpy, creo que debería de usar el que está en verde, este es el Inicio de linea con sus 128 bits de datos, cada dato es un punto de la linea de pantalla de un panel led.

Con el pulso que estoy cogiendo actualmente, en teoría debería de perder 8 bits, aunque no se como gestiona el DMA el buffer cuando se llena, si se van desplazando todos los bits o empieza a llenarse por el principio.


« Última modificación: 09 de Octubre de 2017, 20:36:47 por planeta9999 »

Desconectado KILLERJC

  • Colaborador
  • DsPIC33
  • *****
  • Mensajes: 6050
Re:memcpy - DMA/SPI
« Respuesta #5 en: 09 de Octubre de 2017, 21:07:32 »
Nuevamente no entiendo porque eso debería afectarte en algo.

El SPI recibe el dato completo o varios datos en bytes, una ves recibido el DMA lo transfiere a la memoria, esto lo va a hacer por byte, o cada 2 bytes, o cada 4 segun lo que soporte el SPI/DMA (buffer del SPI y disparo del DMA). El SPI no deberia perder bits, ya que esta dedicado a recibir nomas. y la transferencia por parte del DMA no se hace bit a bit, sino byte a byte, o a 2 bytes, o de a 4 bytes.

Entonces, ¿como es que tenes 2 bits desplazados? La unica que se me ocurre es que se este enviando mal.
Lo que si tambien considero es una posible sobreescritura de datos mientras estes haciendo el memcpy, si es que el DMA esta configurado como un buffer circular, no se si ese micro permite un doble buffer de forma automatica.

Y la otra que estaba pensando, si uno castea el el puntero a uint8_t para sumarle los 7 bytes, el problema luego esta en que el paso de datos se va a tener que realizar en bytes, lo cual es ineficiente para un micro de 32bits, si no mal recuerdo ARM permite el acceso desalineado a memoria, y podria castearlo a punteros de 32bits para aprovechar mejor el tiempo, si es que estas haciendolo en una interrupcion. Pero que en nada afecta a tu problema que tenes y puedo estar equivocado.
Acabo de ver que el Cortex-M4 no puede acceder a 32bits de forma desalineada. Asi que lo anterior es invalido
« Última modificación: 09 de Octubre de 2017, 21:32:03 por KILLERJC »

Desconectado planeta9999

  • Moderadores
  • DsPIC30
  • *****
  • Mensajes: 2736
Re:memcpy - DMA/SPI
« Respuesta #6 en: 10 de Octubre de 2017, 10:52:01 »
Si el DMA carga el buffer por bytes, no por bits, entonces el desplazamiento que veo será por otro motivo. Es probable que esté perdiendo un byte, porque hay un par de instruccións para gestionar el DMA cuando se da por recibida la transacción de datos de una linea, que creo que vacía el sobrante del buffer, son estas:

    // Flush the receive buffer, in case we're out of sync
    SPI0_MCR |= SPI_MCR_HALT;
    SPI0_MCR |= SPI_MCR_CLR_RXF;


En otro caso, quité estás instrucciones, porque se me desplazaba la imagen, y entonces aunque se sigue desplazando hacia la derecha, el trozo que falta por la derecha aparecía por la izquierda.

Una de mis dudas es como funciona el buffer del DMA cuando se llena. ¿ Empieza a sobreescribir por el principio, empuja los datos como si fueran un registro de desplazamiento tirando los más antiguos para dejar sitio a los nuevos, el buffer siempre se llena byte a byte (no bit a bit) .... ??

El memcpy está bien parametrizado, forma parte de una aplicación que está funcionando desde hace tiempo sin problemas, solo que estoy haciendo unos añadidos y como todavía no domino el DMA, ando un poco perdido haciendo pruebas.

El único problema que veo es cuando voy a la función que copia del buffer del DMA y lo lleva a una matriz mía de trabajo, pensaba que se podía copiar desde cualquier bit, pero es por bytes o por el tamaño de los campos de la matriz, en este caso numéricos sin signo de 16 bits.

Esta aplicación está recibiendo constantemente datos por SPI, vinculados a DMA que los vuelca a un buffer. En mi programa hay una interrupción relacionada con un pulso de sincronismo que indica el inicio de una linea de la imagen, cuando llega ese pulso, la interrupción hace que salte a una función que ejecuta entre otras cosas el memcpy para capturar lo que el buffer del DMA ha llenado, y de ahí ya lo llevo a otras rutinas que alimentan el panel led.

Todo eso está funcionando perfectamente para otras máquinas, ahora lo estoy implementando para la máquina de otro fabricante, que tiene algunas diferencias en el formato de las señales, pero la filosofía es la misma. La imagen se recibe por lineas, cada linea tiene 128 puntos, hay pulsos de sincronismo de inicio de imagen, inicio de linea y salto de linea. Los datos se reciben por SPI, con su correpondiente señal de reloj, tengo que capturar cada linea completa cuando el DMA ha llenado su buffer.


Veo algunas cosas de la configuración del DMA, que todavía desconozco para que sirven.

  // Make sure FIFO drain preempts display stuff
  DMAPriorityOrder(*dmaSPI0rx, dmaOutputAddress);
  DMAPriorityOrder(*dmaSPI0rx, dmaUpdateAddress);
  DMAPriorityOrder(*dmaSPI0rx, dmaUpdateTimer);
  DMAPriorityOrder(*dmaSPI0rx, dmaClockOutData);
  enableDMAPreemption(dmaOutputAddress);
  enableDMAPreemption(dmaUpdateAddress);
  enableDMAPreemption(dmaUpdateTimer);
  enableDMAPreemption(dmaClockOutData);


  // Habilitar solicitud de drenaje FIFO DMA (Enable FIFO Drain Request DMA)
  SPI0_RSER = SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS;
  dmaSPI0rx = new DMAChannel();
  dmaSPI0rx->source((volatile uint16_t&) SPI0_POPR);



Una vez se han recibido los datos, y se hace el memcpy para volcarlos a una matriz de trabajo, se ejecutan estas instrucciones relacionadas con el DMA, creo que es para borrar el buffer

    // Flush the receive buffer, in case we're out of sync
    SPI0_MCR |= SPI_MCR_HALT;
    SPI0_MCR |= SPI_MCR_CLR_RXF;

    // Reset the DMA transfer
    dmaSPI0rx->disable();
    dmaSPI0rx->clearComplete();
    dmaSPI0rx->destinationBuffer(plane_buffer, ROW_LENGTH * sizeof(uint16_t)); // ORIGINAL
    dmaSPI0rx->enable();

« Última modificación: 10 de Octubre de 2017, 11:10:00 por planeta9999 »

Desconectado KILLERJC

  • Colaborador
  • DsPIC33
  • *****
  • Mensajes: 6050
Re:memcpy - DMA/SPI
« Respuesta #7 en: 10 de Octubre de 2017, 16:25:09 »
El DMA se maneja con una prioridad fija o una secuencia round-robing, por lo tanto comienza desde el canal 1 si es que esta round-robing.

DMAPriorityOrder simplemente hace un cambio de prioridades, dejando siempre al primer argumento con mayor prioridad ( menor canal ).
Por lo tanto la idea es que dmaSPI0rx tenga la mayor prioridad en el DMA, el codigo por si lo buscas:

Código: C
  1. void DMAPriorityOrder(DMAChannel &ch1, DMAChannel &ch2)
  2. {
  3.         if (priority(ch1) < priority(ch2)) swap(ch1, ch2);
  4. }
  5.  
  6. static uint32_t priority(const DMAChannel &c)
  7. {
  8.         return 3 - c.channel;
  9. }

La funcion enableDMAPreemption hace que aquellas transferencias habilitadas, puedan ser interrumpidas por otra transferencia de mayor prioridad, suponete que tenes el UpdateTimer transfiriendose y se da el disparo del SPI, entonces dejaria lo que estaba haciendo para atender el del SPI

----------------

Código: C
  1.   // Habilitar solicitud de drenaje FIFO DMA (Enable FIFO Drain Request DMA)
  2.   SPI0_RSER = SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS;
  3.   dmaSPI0rx = new DMAChannel();
  4.   dmaSPI0rx->source((volatile uint16_t&) SPI0_POPR);

Esa parte del codigo, lo que hace al editar el registro SPI0_RSER, es habilitar primero la interrupcion al momento que el buffer del SPI ( que posee 4 lugares ) no este lleno. Y luego ademas le dice que no genere una interrupcion sino que genere una solicitud al DMA.
Finalmente crea el objeto DMA que posee toda la informacion ( descriptor ) y le dice donde sacar el dato, que es atraves del registro SPI0_POPR en cual funciona como un stack, donde haces un POP del buffer del SPI y vas sacando de a bytes.

--------------------------------------

Código: C
  1.     // Flush the receive buffer, in case we're out of sync
  2.     SPI0_MCR |= SPI_MCR_HALT;
  3.     SPI0_MCR |= SPI_MCR_CLR_RXF;

Basicamente detiene cualquier transferencia del SPI (TX,RX) y hace un flush de los buffer de Recepcion, limpiando tambien los contadores de cantidad de datos que poseen.
Pero si esta configurado como modo continuo entonces no hace nada.

--------------------------------------

Como bien dice, Resetea la trnasferencia.

Código: C
  1.     // Reset the DMA transfer
  2.     dmaSPI0rx->disable();
  3.     dmaSPI0rx->clearComplete();
  4.     dmaSPI0rx->destinationBuffer(plane_buffer, ROW_LENGTH * sizeof(uint16_t)); // ORIGINAL
  5.     dmaSPI0rx->enable();

Deshabilita, Si habia terminado, limpia el flag de completo del descriptor, asi le permite cargar nuevamente la cantidad a enviarse.
Le agrega la direccion de destino, y la cantidad de veces a repetir el mismo traslado de 1 byte.
Y finalmente lo habilita nuevamente.

------------------------------------------------------------------------

A lo que yo apunto es que Supongamos que tenes un SPI perfecto, Y este es tu esclavo. Partimos de la suposicion de que llega todo correctamente, es edcir desde el primer bit al ultimo enviado al SPI. Entonces apenas llegue el primer byte, este va a pasar a la FIFO del SPI, el flag va a indicar que no esta vacio, por lo tanto va a disparar el DMA, el DMA toma el byte y lo lleva a su correspondiente direccion Y listo...
Ves, todo es por "bytes", entonces el unico momento que se me ocurre que pueda ocurrir

Desconectado planeta9999

  • Moderadores
  • DsPIC30
  • *****
  • Mensajes: 2736
Re:memcpy - DMA/SPI
« Respuesta #8 en: 10 de Octubre de 2017, 17:00:45 »
 

Gracias por la información Killer.

Si todo se recibe y procesa por bytes, entonces probablamente tengo perdido 1 byte, por llegar tarde a coger los datos del buffer del DMA. Eso lo hago por una interrupción ligada a un pulso de sincronismo, lo que pasa es que hay varios pulsos de sincronismo y varian entre máquinas, de manera que lo que puede funncionar en una, no lo hace bien en otra.

En este caso concreto, el pulso de sincronismo que se estaba utilizando me parece que no es el adecuado, voy a probar con otros, en concreto con el pulso de sincronismo por inicio de linea, de esa manera iré a la función que hace el memcpy, justo cuando acaba una linea y empieza la siguiente, así debería de tener los datos completos de la linea recien recibida.

Esto del DMA es muy interesante, extremádamente potente. El problema es que no encuentro una documentación seria y detallada, algo que explique al detalle como se configura el DMA, al menos en los STM32 y los Kinetis que son los que más uso. El DMA me parece algo imprescindible en muchos casos, permite hacer cosas que de otro modo solo se podrían hacer con otra tecnología más cara y complicada como los FPGA.

¿ No sabrás por casualidad, donde encontrar una buena documentacion, algún libro, cursillo, que trate específicamente sobre el DMA ?.

Me voy formando, en base a leer fuentes, presuponer o adivinar algunas cosas, pero muchas otras me suenan todavía a chino. Tengo claro el concepto de lo que es el DMA y para que sirve, pero me falta el detalle, para saber como configurarlo exactamente, con todos sus parámetros y opciones. Por más que he buscado no he encontrado nada, todo es muy confuso y poco detallado.
« Última modificación: 10 de Octubre de 2017, 17:04:17 por planeta9999 »

Desconectado KILLERJC

  • Colaborador
  • DsPIC33
  • *****
  • Mensajes: 6050
Re:memcpy - DMA/SPI
« Respuesta #9 en: 10 de Octubre de 2017, 23:03:36 »
La verdad que no tengo ningun libro sobre el DMA, lo que uso esta en el datasheet, el problema que veo en el datasheet de NXP es que explica muy pero muy poco.
Y otro problema a pesar de que todos tienen las mismas caracteristicas basicas, entre fabricantes cada uno tiene lo suyo. Ejemplo podes ver que el de ST ofrece un "Doble Buffer" el cual te permite definir 2 buffer, en el cual se llena uno, genera una interrupcion y comienza solo a llenar el otro, termina ese y comienza de nuevo con el primero.

ST que ofrece un PDF con explicaciones, distinto a NXP:

http://www.st.com/content/ccc/resource/technical/document/application_note/27/46/7c/ea/2d/91/40/a9/DM00046011.pdf/files/DM00046011.pdf/jcr:content/translations/en.DM00046011.pdf

Igual en los Reference Manual esta bien explicado el funcionamiento y las formas que tienen de funcionar el DMA.
« Última modificación: 10 de Octubre de 2017, 23:10:44 por KILLERJC »

Desconectado tsk

  • PIC16
  • ***
  • Mensajes: 156
Re:memcpy - DMA/SPI
« Respuesta #10 en: 11 de Octubre de 2017, 00:44:35 »
Otras fuentes las encuentras en el manual de referencia, está todo en el capitulo 23 (DMAMUX) y 24 (eDMA)

https://www.nxp.com/docs/en/reference-manual/K66P144M180SF5RMV2.pdf

Y pues el mismo código fuente del SDK de los Kinetis.

También veo que teensy está usando eDMA, por lo que si te descargas el SDK del Kinetis en el código fuente verás los siguientes archivos que son de interés

fsl_edma.(h,c)
fsl_dspi.(h,c)
fsl_dspi_edma.(h,c)

Otras fuentes

https://community.nxp.com/docs/DOC-102981

Aunque al igual que las hojas de datos, la información está algo escaza.

Otra información la puedes obtener de

http://cache.freescale.com/files/32bit/doc/quick_ref_guide/KQRUG.pdf

en la página 65 que inicia con lo del eDMA

Desconectado planeta9999

  • Moderadores
  • DsPIC30
  • *****
  • Mensajes: 2736
Re:memcpy - DMA/SPI
« Respuesta #11 en: 11 de Octubre de 2017, 05:56:38 »

Gracias, miraré todos esos documentos. El problema es que Datasheet y Manuales de referencia suelen ser muy crípticos y espartanos, sin un miserable ejemplo.

Se aprende mucho más leyendo algún codigo fuente, sobre todo si está documentado, que con los datasheet.

Tambien ando buscando lo mismo para los Atmel S70, y nada. Me lo voy a tener que currar leyendo codigo fuente que encuentre por ahí, y probando, para ir aprendiendo. Es casi un trabajo de ingenieria inversa.
« Última modificación: 11 de Octubre de 2017, 06:01:07 por planeta9999 »

Desconectado planeta9999

  • Moderadores
  • DsPIC30
  • *****
  • Mensajes: 2736
Re:memcpy - DMA/SPI
« Respuesta #12 en: 11 de Octubre de 2017, 07:02:50 »
 
Encontré este enlace, en el que parece que detallan bastante como configurar le DMA para los Kinetis, en el ejemplo para un MK64 que controla unos led digitales WS2811. Por ahora es el tipo de documentación que más se aproxima a lo que busco, ejemplos prácticos y con explicaciones de lo que están haciendo.

https://mcuoneclipse.com/2015/08/05/tutorial-adafruit-ws2812b-neopixels-with-the-freescale-frdm-k64f-board-part-5-dma/


Y para los Atmel SAM S70, encontré estos ejemplos para el ASF
https://github.com/avrxml/asf/tree/master/sam/drivers/spi


A ver si a base de leer y probar fuentes, me voy enterando, es la forma que más me gusta de aprender, con ejemplos prácticos
« Última modificación: 11 de Octubre de 2017, 07:10:02 por planeta9999 »

Desconectado planeta9999

  • Moderadores
  • DsPIC30
  • *****
  • Mensajes: 2736
Re:memcpy - DMA/SPI
« Respuesta #13 en: 15 de Octubre de 2017, 22:51:26 »
 
Arreglé mi problema. Al final el desplazamiento de 3 pixels a la derecha, que sufría en la pantalla led, si que era tema del DMA. Ha sido meter este bucle de retardo con "nop", antes de hacer el memcpy para copiar el buffer de DMA a una matriz de trabajo, y listo, ahora me va a la perfección.

      for (int i = 0; i < 29; i++)
      {
        asm volatile("nop\n");
      }


De lo que deduzco, que la carga de datos en el buffer del DMA, no se hace por bytes, sino por bits (al menos por SPI).

El problema es que la señal de sincronismo que usaba, para saltar por una interrupción, a la rutina que captura el buffer del DMA, no llegaba en el momento exacto y por eso me faltaban unos bits, con este pequeño retardo que he añadido, después de jugar un poco con el valor del bucle, me va perfecto. Además antes, además del desplazamiento, me temblaban los pixels y me salían pixels fantasma, era algo inestable.

« Última modificación: 15 de Octubre de 2017, 22:55:16 por planeta9999 »

Desconectado KILLERJC

  • Colaborador
  • DsPIC33
  • *****
  • Mensajes: 6050
Re:memcpy - DMA/SPI
« Respuesta #14 en: 15 de Octubre de 2017, 23:59:38 »
O sea que implementaste una demora para que el DMA termine de transferir los datos y recien alli hacer el memcpy?.

Pense que usabas una interrupcion para saber cuando es que el DMA finaliza de enviar los datos y asi moverlos con el memcpy.. Y aca mi miedo era una posible sobre escritura de alguna posicion.
Realmente sigo sin entender, y estoy mas que seguro que el problema es otro a pesar que esto lo solucione, la transferencia del DMA es por bytes, no tiene otro sentido, es decir por hardware no vas a implementar un shift register en todas las posiciones de memoria RAM, y en los perifericos, mas en una FIFO que posee el SPI.

Ademas eso implicaria que el procesador tambien cuando necesite leer un registro de un periferico y/o memoria RAM lo haga de esa forma.