Autor Tema: Debugueando un driver SPI - uso del DMA  (Leído 2830 veces)

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

Desconectado elgarbe

  • Moderador Local
  • PIC24H
  • *****
  • Mensajes: 2178
Debugueando un driver SPI - uso del DMA
« en: 29 de Marzo de 2016, 11:11:33 »
Buenas, esta es la tercera vez que me toca toparme con una "limitacion" en el driver del bus SPI de un micro ARM. Esta situacion que voy a detallar me ha pasado con ST y con NXP, los ARM que uso normalmente. Voy a tratar de explicar el paso a paso para que se entienda lo que veo y como lo he tenido que solucionar.

El ptoyecto que voy a tratar de mejorar es el del cartel de LED RGB (http://www.todopic.com.ar/foros/index.php?topic=43643.0), el cual voy a actualizar otro día ya que lo tengo funcionando.
En ese cartel, tengo 4 lineas para decodificar 1 de las 16 filas. Luego los drive de los led (TLC5954) necesitan 3 o 4 lineas, una de clock, una de data, una de latch y una de blank.
Mi programa tiene 2 buffer "graficos", uno que coincide con la cantidad de pixeles del cartel, el cual es el buffer donde yo puedo escrivir o cargar imágenes y el segundo es el buffer del SPI, el cual tiene la informacion tal cual la necesitan los TLC, incluyendo el color de cada pixel.
Luego tengo un timer, el cual me interrumpe cada vez que quiero actualizar una fila. Este timer tiene que correr a 16x frecuencia de Frame deseada. Por ejemplo, si quiero trabajar a 120Hz voy a necesitar escribir en cada fila en 1/(16*120)= 520.5 useg o lo que es lo mismo, cada 520.5 useg voy a tener que escribir una nueva fila. En principio, parece un montón de tiempo. Veamos, para manejar 4 módulos de 32 columnas (128 columnas u 8 TLC) necesito enviar 49*8 bits = 392 bits. Por lo que me da una frecuencia teórica de  752,64KHz en el SPI. Hay que tener en cuenta que hay algunos tiempos adicionales como el tiempo que necesario para cambiar de fila, el tiempo para activar la señal de BLANK, preparar la transferencia, etc. Supongamos que pongo el SPI a 1 MHz, debería ser suficiente.
Bien mi rutina del timer es la siguiente:

Código: C
  1. void TIMER16_0_IRQHandler(void)
  2. {
  3.         if (Chip_TIMER_MatchPending(LPC_TIMER16_0, 1))
  4.         {
  5.                 Chip_TIMER_ClearMatch(LPC_TIMER16_0, 1);
  6.  
  7.                 // Pongo las salidas de los TLC en 0
  8.                 Chip_GPIO_SetPinOutHigh(LPC_GPIO, BLK_PORT, BLK_PIN);
  9.  
  10.                 // Enmascaro todos los bits del puerto 0 excepto los de direccion (A, B, C, D)
  11.                 LPC_GPIO->MASK[0] = ~(0xF << A_PIN);
  12.                 // Aplico el valor binario de la fila directamente al puerto
  13.                 LPC_GPIO->PIN[0] = (15-ui8FilaActual) << A_PIN;
  14.                 //Desenmascaro el puerto
  15.                 LPC_GPIO->MASK[0] = 0;
  16.  
  17.                 Chip_SSP_WriteFrames_Blocking(LPC_SSP1, mem_DMA_01[ui8FilaActual], BYTES_X);
  18.  
  19.                 //una vez enviados los 49 datos hago un latch
  20.                 Chip_GPIO_SetPinOutHigh(LPC_GPIO, LAT_PORT, LAT_PIN);
  21.                 Chip_GPIO_SetPinOutLow(LPC_GPIO, LAT_PORT, LAT_PIN);
  22.  
  23.                 // Pongo las salidas de los TLC en ON
  24.                 Chip_GPIO_SetPinOutLow(LPC_GPIO, BLK_PORT, BLK_PIN);
  25.  
  26.                 // Incremento el índice de fila actual
  27.         ui8FilaActual++;
  28.         // Cuando llego a la fila 16 vuelvo a 0
  29.         if(ui8FilaActual == 16){
  30.                 ui8FilaActual=0;
  31.         }
  32.         }
  33. }

Cuando pruebo el cartel funciona bien. Entonces voy al Analizador lógico y hago una adquisición para ver cuan sobrado de tiempo estoy:


* Captura de pantalla 2016-03-29 10.38.25-min.jpg
(67 kB, 1288x992 - visto 476 veces)


En esta primer adquisicion se puede ver las señales de CLK, DATA, LATCH, BLANK y finalmente las 4 líneas de decodificacion de Fila. Como se ve, el delta X está en 520.5 useg y la medicion de frecuencia donde apunto con el mouse (CLK) es de 1 MHZ). En principio está todo como lo configuré.
Haciendo un zoom para ver una transmision entera obtengo:


* Captura de pantalla 2016-03-29 10.40.22-min.jpg
(72.3 kB, 1288x992 - visto 473 veces)


He puesto los cursores para ver que me sobran 42 useg al finalizar la transmision. Por ahora estoy usando una funcion "bloqueante" del SPI.
Bien, el tema es que este cartel posee el doble de módulos, por lo que a 1 MHZ no voy a poder trabajar. Voy a tener que pasar a 2 MHz.
Hago el cambio y vuelvo a adquirir con el analizador lógico:


* Captura de pantalla 2016-03-29 10.51.39-min.jpg
(68.75 kB, 1288x992 - visto 459 veces)


Aquí empieza a aparecer el problema que quiero compartir. Yo aumenté la velocidad del SPI al doble, pero sigo enviando la misma cantidad de bytes, 49. Uno pensaría que tendría que sobrarme más de 250 useg (cada cambio dura 520useg) sin embargo solo me sobran 180 useg!!!!! si ahí quisiera meter 49 bytes más no podría. En la captura se ve bien que no entrarían el doble de bytes. Por que???? Uno pensaría que en el SPI a 2 MHz puede sacar 49 bytes en 49 * 8 * 0.5 useg = 196 useg, sin embargo acá me esta tomando alrededor de 322 uSeg. Esta situacion se hace más notoria a medida que voy subiendo la velocidad de CLK del SPI. Y esto mismo me ha pasado en varias oportunidades con 2 micros de marcas distintas.
Veamos un poco el detalle del CLK de la transmision del SPI:


* Captura de pantalla 2016-03-29 10.58.56-min.jpg
(64.54 kB, 1288x992 - visto 475 veces)


He aquí el problema. Cada 8 bits que envía, necesita casi 3 useg para hacer no se qué. O sea, envía 8 bits y le toma 4 useg, luego necesita 3 useg muertos y luego puede continuar con los siguientes 8 bits. Esos 3 useg, con tasas mayores hacen que la cosa no funcione. Por ejemplo, si pongo el SPI a 6 MHz tengo 1.25 useg para enviar 8bits y luego 5.4 useg de tiempo muerto!!!!

En la próxima, la solucion....
-
Leonardo Garberoglio

Desconectado juaperser1

  • Colaborador
  • DsPIC30
  • *****
  • Mensajes: 2979
Re:Debugueando un driver SPI - uso del DMA
« Respuesta #1 en: 29 de Marzo de 2016, 11:37:58 »
Esos son los típicos problemas que te hacen calentarte la cabeza durante mucho tiempo, sin saber que narices esta pasando.

¿Oye Leonardo has pensado en usar una FPGA? Se que seria hacer el hardware de nuevo, pero quizá te compense, ya sea por el proceso en paralelo y por que supongo que podrás hacer el barrido de manera mucho mejor ¿no?

El problema, me da que puede venir por que la librería tiene una espera de vaciado de buffer o algo por el estilo, esperare a ver que comentas.

un saludo

Visita mi canal para aprender sobre electrónica y programación:

https://www.youtube.com/channel/UCxOYHcAMLCVEtZEvGgPQ6Vw

Desconectado elgarbe

  • Moderador Local
  • PIC24H
  • *****
  • Mensajes: 2178
Re:Debugueando un driver SPI - uso del DMA
« Respuesta #2 en: 29 de Marzo de 2016, 13:08:13 »
Esos son los típicos problemas que te hacen calentarte la cabeza durante mucho tiempo, sin saber que narices esta pasando.

¿Oye Leonardo has pensado en usar una FPGA? Se que seria hacer el hardware de nuevo, pero quizá te compense, ya sea por el proceso en paralelo y por que supongo que podrás hacer el barrido de manera mucho mejor ¿no?

El problema, me da que puede venir por que la librería tiene una espera de vaciado de buffer o algo por el estilo, esperare a ver que comentas.

un saludo

Hola Juanjo, las FPGA son materia pendiente. Por ahora estoy adquiriendo el know how en esto de la cartelería. Por ahora tengo que sacarle el jugo y ver cuál es el limite de esta implementacion. En un futuro no descarto el uso de ellas.

más o menos por ahí viene el tema... lo estoy documentando asi lo comparto.

Abrazo
-
Leonardo Garberoglio

Desconectado KILLERJC

  • Colaborador
  • DsPIC33
  • *****
  • Mensajes: 8242
Re:Debugueando un driver SPI - uso del DMA
« Respuesta #3 en: 29 de Marzo de 2016, 13:23:46 »
Ahh esa es una materia pendiente que tengo! Comprar un analizador logico :P jeje, para colmo nada baratos son..

Desconectado elgarbe

  • Moderador Local
  • PIC24H
  • *****
  • Mensajes: 2178
Re:Debugueando un driver SPI - uso del DMA
« Respuesta #4 en: 29 de Marzo de 2016, 13:35:06 »
Ahh esa es una materia pendiente que tengo! Comprar un analizador logico :P jeje, para colmo nada baratos son..

naaaaaaaaaaaa, el mio es un clon del USBee, me costó USD10 hace unos años!!!  :D :D :D
Pero para estas cosas son geniales!!!!
-
Leonardo Garberoglio

Desconectado elgarbe

  • Moderador Local
  • PIC24H
  • *****
  • Mensajes: 2178
Re:Debugueando un driver SPI - uso del DMA
« Respuesta #5 en: 29 de Marzo de 2016, 13:52:46 »
Bueno, presentemos el problema. Yo le pondría como título "Las capas de software y el código standar"
El tema es que los creadores de las librerías de software (Cube en ST y LPCOpen en NXP) tratan de hacer funciones que sirvan para todo y que cubran la mayor parte de las necesidades básicas. Pero eso hace que las funciones tengan mil chequeos y que hagan cosas innecesarias en algunos casos.
Veamos la implementacion de la funcion:

Código: C
  1. /* SSP Polling Write in blocking mode */
  2. uint32_t Chip_SSP_WriteFrames_Blocking(LPC_SSP_T *pSSP, uint8_t *buffer, uint32_t buffer_len)
  3. {
  4.         uint32_t tx_cnt = 0, rx_cnt = 0;
  5.  
  6.         /* Clear all remaining frames in RX FIFO */
  7.         while (Chip_SSP_GetStatus(pSSP, SSP_STAT_RNE)) {
  8.                 Chip_SSP_ReceiveFrame(pSSP);
  9.         }
  10.  
  11.         /* Clear status */
  12.         Chip_SSP_ClearIntPending(pSSP, SSP_INT_CLEAR_BITMASK);
  13.  
  14.         if (Chip_SSP_GetDataSize(pSSP) > SSP_BITS_8) {
  15.                 uint16_t *wdata16;
  16.  
  17.                 wdata16 = (uint16_t *) buffer;
  18.  
  19.                 while (tx_cnt < buffer_len || rx_cnt < buffer_len) {
  20.                         /* write data to buffer */
  21.                         if ((Chip_SSP_GetStatus(pSSP, SSP_STAT_TNF) == SET) && (tx_cnt < buffer_len)) {
  22.                                 Chip_SSP_SendFrame(pSSP, *wdata16);
  23.                                 wdata16++;
  24.                                 tx_cnt += 2;
  25.                         }
  26.  
  27.                         /* Check overrun error */
  28.                         if (Chip_SSP_GetRawIntStatus(pSSP, SSP_RORRIS) == SET) {
  29.                                 return ERROR;
  30.                         }
  31.  
  32.                         /* Check for any data available in RX FIFO */
  33.                         while (Chip_SSP_GetStatus(pSSP, SSP_STAT_RNE) == SET) {
  34.                                 Chip_SSP_ReceiveFrame(pSSP);    /* read dummy data */
  35.                                 rx_cnt += 2;
  36.                         }
  37.                 }
  38.         }
  39.         else {
  40.                 uint8_t *wdata8;
  41.  
  42.                 wdata8 = buffer;
  43.  
  44.                 while (tx_cnt < buffer_len || rx_cnt < buffer_len) {
  45.                         /* write data to buffer */
  46.                         if ((Chip_SSP_GetStatus(pSSP, SSP_STAT_TNF) == SET) && (tx_cnt < buffer_len)) {
  47.                                 Chip_SSP_SendFrame(pSSP, *wdata8);
  48.                                 wdata8++;
  49.                                 tx_cnt++;
  50.                         }
  51.  
  52.                         /* Check overrun error */
  53.                         if (Chip_SSP_GetRawIntStatus(pSSP, SSP_RORRIS) == SET) {
  54.                                 return ERROR;
  55.                         }
  56.  
  57.                         /* Check for any data available in RX FIFO */
  58.                         while (Chip_SSP_GetStatus(pSSP, SSP_STAT_RNE) == SET && rx_cnt < buffer_len) {
  59.                                 Chip_SSP_ReceiveFrame(pSSP);    /* read dummy data */
  60.                                 rx_cnt++;
  61.                         }
  62.                 }
  63.         }
  64.  
  65.         return tx_cnt;
  66.  
  67. }
  68. }

Como primer tema, es una sola implementacion tanto si estamos con frame de 8 o más bits. Yo solo uso frames de 8 bits, por lo que solo quedaría algo así:

Código: C
  1. uint32_t Chip_SSP_WriteFrames_Blocking(LPC_SSP_T *pSSP, uint8_t *buffer, uint32_t buffer_len)
  2. {
  3.         uint32_t tx_cnt = 0, rx_cnt = 0;
  4.  
  5.         /* Clear all remaining frames in RX FIFO */
  6.         while (Chip_SSP_GetStatus(pSSP, SSP_STAT_RNE)) {
  7.                 Chip_SSP_ReceiveFrame(pSSP);
  8.         }
  9.  
  10.         /* Clear status */
  11.         Chip_SSP_ClearIntPending(pSSP, SSP_INT_CLEAR_BITMASK);
  12.  
  13.         uint8_t *wdata8;
  14.  
  15.         wdata8 = buffer;
  16.  
  17.         while (tx_cnt < buffer_len || rx_cnt < buffer_len) {
  18.                 /* write data to buffer */
  19.                 if ((Chip_SSP_GetStatus(pSSP, SSP_STAT_TNF) == SET) && (tx_cnt < buffer_len)) {
  20.                         Chip_SSP_SendFrame(pSSP, *wdata8);
  21.                         wdata8++;
  22.                         tx_cnt++;
  23.                 }
  24.  
  25.                 /* Check overrun error */
  26.                 if (Chip_SSP_GetRawIntStatus(pSSP, SSP_RORRIS) == SET) {
  27.                         return ERROR;
  28.                 }
  29.  
  30.                 /* Check for any data available in RX FIFO */
  31.                 while (Chip_SSP_GetStatus(pSSP, SSP_STAT_RNE) == SET && rx_cnt < buffer_len) {
  32.                         Chip_SSP_ReceiveFrame(pSSP);    /* read dummy data */
  33.                         rx_cnt++;
  34.                 }
  35.         }
  36.         return tx_cnt;
  37. }

El segundo punto, que es donde realmente estan los tiempos muertos es en las lecturas, estimo que las hace para mantener el buffer de recepcion vacío. Pero en mi caso yo no uso la recepcion de datos, por lo que puedo eliminar toda esa parte, quedando la implementacion de este modo:

Código: C
  1. uint32_t SendSPI(LPC_SSP_T *pSSP, uint8_t *buffer, uint32_t buffer_len)
  2. {
  3.         uint32_t tx_cnt = 0;
  4.         uint8_t *wdata8;
  5.         wdata8 = buffer;
  6.  
  7.         while (tx_cnt < buffer_len) {
  8.                 /* write data to buffer */
  9.                 if ((Chip_SSP_GetStatus(pSSP, SSP_STAT_TNF) == SET) && (tx_cnt < buffer_len)) {
  10.                         Chip_SSP_SendFrame(pSSP, *wdata8);
  11.                         wdata8++;
  12.                         tx_cnt++;
  13.                 }
  14.         }
  15.         // No vuelvo hasta que el SPI halla vaciado el FIFO y deje de transmitir
  16.         while(Chip_SSP_GetStatus(pSSP, SSP_STAT_BSY) == 1);
  17.  
  18.         return tx_cnt;
  19. }

Ahora sí, cuando pongo el SPI a 2MHz obtengo que el envío de cada byte tarda 4 useg y tengo un tiempo muerto de 0.75 useg entre byte y byte.
Pero con el SPI a 8 MHz obtengo 1 useg para enviar 1 bytes y tengo 1.4 useg de tiempo muerto!!!!!!!!!
Seguramente se podrá mejorar un poco la funcion de envío de datos, pero evidentemente a alta velocidad, el bus queda muy desaprovechado....

La solucion? DMA!!!!!!!!

En la próxima los resultados....
-
Leonardo Garberoglio

Desconectado juaperser1

  • Colaborador
  • DsPIC30
  • *****
  • Mensajes: 2979
Re:Debugueando un driver SPI - uso del DMA
« Respuesta #6 en: 29 de Marzo de 2016, 16:00:14 »
a mi me paso algo parecido con las MLA de microchip entre el I2C y la flash, se pisaban unos a otros sin tener por que, y era culpa de las librerias tambien, para cosas tan especificas no te puedes fiar de las librerias, mejor si las haces tu y si es en ensamblador mejor.

pero con el DMA yo creo que debe arreglarse el problema a ver que tal.

Citar
Hola Juanjo, las FPGA son materia pendiente.

ya somos dos, pero digo a ver si Leonardo empieza y me hace el trabajo sucio  :D :D

un saludo
Visita mi canal para aprender sobre electrónica y programación:

https://www.youtube.com/channel/UCxOYHcAMLCVEtZEvGgPQ6Vw

Desconectado elgarbe

  • Moderador Local
  • PIC24H
  • *****
  • Mensajes: 2178
Re:Debugueando un driver SPI - uso del DMA
« Respuesta #7 en: 30 de Marzo de 2016, 11:29:07 »
El DMA debe ser el periferico más interesante de un micro, el que nos permite sacarle toda la potencial al mismo, pero al mismo tiempo debe ser el menos usado, por su dificultad inicial para entenderlo y configurarlo.
Mi caso no ha sido la excepcion. Entender y configurar un canal DMA en el LPC11u67 usando las LPCOpen me ha llevado unas cuantas horas y aún no lo tengo del todo funcional.
Debo decir que configurar y usar el DMA en los micros ST usando el CubeMX es de lo más fácil. Configurar el DMA con una utilidad gráfica con explicacion de que es cada cosa no tiene precio. Es cuestion de minutos para tener el DMA funcionando...

Bien, a los resultados. La siguiente imagen muestra una primer transferencia usando DMA y las subsiguientes usando el SPI en modo polling:


* dma.jpg
(85.8 kB, 1032x776 - visto 428 veces)


Una vista con zoom en la transferencia del DMA:


* DMA2.jpg
(88.51 kB, 1032x776 - visto 411 veces)


Como se ve tenemos solo 250nseg - (1/8.000.000)/2 = 187.5 nseg de tiempo muerto entre una transferencia y otra.

Si miramos la transferencia con polling:


* POLLING.jpg
(84.93 kB, 1032x776 - visto 451 veces)


Tenemos mas o menos 1.54 useg, mas tiempo muerto que tiempo enviando datos!

En resumen, cuando estamos usando el SPI o modulos de comunicacion a alta velocidad (un par de MHz) se debe habilitar el DMA para permitir que la tasa de transferencia sea la teórica. De otor modo, resulta muy dificil (habría que probar con alguna rutina en assembler quizá) conseguir buenos tiempos de transferencia.
Espero le sirva a alguien, yo necesitaba documentarlo.

Saludos
-
Leonardo Garberoglio

Desconectado KILLERJC

  • Colaborador
  • DsPIC33
  • *****
  • Mensajes: 8242
Re:Debugueando un driver SPI - uso del DMA
« Respuesta #8 en: 30 de Marzo de 2016, 12:02:39 »
Si el DMA es el mas interesante, pero ademas tenes que reunir ciertas condiciones, como lo es tener un buffer con el cual alimentar el DMA. En tu caso es ideal. Por que realmente lo tenes. Yo tengo mi libreria TFT realizada con puertos, me parece una barbarie realmente, asi que estuve pensando pasarla al puerto paralelo ( EPI ) que posee el micro desde hace rato. Pero me encuentro con ese problema, yo no poseo un buffer como para eso.. Tal ves necesite hacer un pequeño buffer como para aguantar todos los datos posibles que pueda enviar, pero 320*240*2 = 153Kb, lo cual no es nada lindo para desperdiciar en un micro mientras no se usa. Y que el buffer sea mas chico implica que tenga que esperar a que se envie las demas cosas para poder llenar el buffer con datos nuevos. Lo cual nuevamente estoy bloqueando todo el programa :P

Desconectado elgarbe

  • Moderador Local
  • PIC24H
  • *****
  • Mensajes: 2178
Re:Debugueando un driver SPI - uso del DMA
« Respuesta #9 en: 30 de Marzo de 2016, 13:33:20 »
Si en la TFT queres mostrar una imagen fija, no tiene mucho uso el DMA, pero si es una animacion o hay interaccion con el usuario y tenes que redibujar seguido, creo que el DMA es muy útil. En muchos TFT no necesitas tener 2 buffer por que ellos incluyen 2 layer de dibujo creo.
Pero ojo, que el DMA podes usarlo con pocos datos tambien. Por ejemplo enviar datos por el uart en un data logger. No importa que sean pocos datos, aunque quieras enviar 3 o 4 sensores (supongamos floats) y por lo tanto sean 16 bytes a enviar, si son lecturas repetitivas el DMA es muy util.

La otra aplicacion donde lo necesite fue en un front end de TI para EEG. El ADC interno es de 24bits y creo que tenía 8 cabales + status, por lo que eran 24*8 + 4*8 = 224 bits. La frecuencia máxima de muestreo era de 16KHz y el SPI puede ir hasta 10MHz... Una pavada dije y lo encaré con el SPI en polling.
224 * 16.000 = 3 584 000 bits por segundo. Estoy sobrado con el SPI me dije. Bueno, no hubo forma de hacerlo andar. No optimicé demaciado la rutina de envío por el SPI, pero fijate que tenía mas del doble de frecuencia disponible y aunque puse el SPI a 10MHz, no llegaba a leer los 224 bits entre muestras!!! Como era un micro de ST y configurar y usar el DMA es muy simple (con el cubemx), no renegué optimizando el polling. Pero me costó unas cuantas horas (dias quizá) descubrir el porque no andaba. Las cuentas me daban bien!!!!

Saludos
-
Leonardo Garberoglio