Autor Tema: Otra vez el DMA para el SPI  (Leído 1553 veces)

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

Desconectado elgarbe

  • Moderador Local
  • PIC24H
  • *****
  • Mensajes: 2178
Otra vez el DMA para el SPI
« en: 07 de Mayo de 2017, 15:57:57 »
Ya en alguna oportunidad habia mostrado algo de como el DMA mejora notablemente la tasa de transferencia del SPI.
Hoy estoy jugando otra vez con unos paneles LED para un proyectito y cuando metí el DMA para la transferencia me asombré denuevo.
Otra cosa que me asombró es la facilidad con que uno puede agregar DMA en el SPI con el CubeMX. Me ha tocado configurar un canal DMA en un LPC y con las librerías LPCOpen, muy bien escritas pero con funcionalidades basicas, me ha costado muchísimo.

Esto es que hay que hacer en CubeMX para habilitar el DMA:

Primero les muestro la parte del código que usaba el SPI en modo bloqueo.

Código: C
  1. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
  2. {
  3.         // Pongo las salidas de los TLC en 0
  4.         HAL_GPIO_WritePin(BLK_GPIO_Port, BLK_Pin, GPIO_PIN_SET);
  5.  
  6.         //Hago el barrido vertical. Tomo el Puerto A y pongo en 0 los cuatro LSB (A, B, C, D)
  7.         A_GPIO_Port->ODR &= ~(A_GPIO_Port->IDR & 0x000F);
  8.         //Luego meto el valor de la fila actual en los 4 LSB
  9.         A_GPIO_Port->ODR |= (15-ui8FilaActual);
  10.  
  11.         //Envío los datos
  12.         HAL_SPI_Transmit_DMA(&hspi2, mem_DMA[ui8FilaActual], BYTES_X);
  13.  
  14.         //una vez enviados los datos hago un latch
  15.         HAL_GPIO_WritePin(LAT_GPIO_Port, LAT_Pin, GPIO_PIN_SET);
  16.         HAL_GPIO_WritePin(LAT_GPIO_Port, LAT_Pin, GPIO_PIN_RESET);
  17.  
  18.         // Pongo las salidas de los TLC en el valor deseado
  19.         HAL_GPIO_WritePin(BLK_GPIO_Port, BLK_Pin, GPIO_PIN_RESET);
  20.  
  21.         // Incremento el índice de fila actual
  22.         ui8FilaActual++;
  23.         // Cuando llego a la fila 16 vuelvo a 0
  24.         if(ui8FilaActual == 16)
  25.                 ui8FilaActual=0;
  26. }

Básicamente tengo un timer que desborda cada vez que paso de una línea a la otra de la pantalla. Está configurado en unos 260useg. Dentro de la interrupcion tengo que deshabilitar las salidas del drive de los LED, luego cambiar de fila, luego enviar todos los datos, habilitar las salidas, incrementar las filas y volver. Para tener un buen refresco hay que hacer esto lo más rápido posible.
En este caso el SPI está a 18MHz con el micro corriendo a 64MHz

Bien, con ese código obtengo esto en el analizador:


* SIN_DMA.png
(64.47 kB, 1299x399 - visto 398 veces)


La señal marrón es la señal de BLK, la blanca hace un toggle por cada interrupcion. La roja son los pulsos de clk del SPI.
Si hacemos un zoom sobre los pulsos de clk tenemos:


* SIN_DMA_ZOOM.png
(48.92 kB, 746x362 - visto 373 veces)


Se puede ver como los grupos de envíos de 8 bits están separados por tiempo muerto. Tiempo que por mejor librería que tengamos no se puede reducir, o por lo menos no tanto como con el uso de DMA.

Bien, para habilitar el DMA lo unico que tenemos que hacer en el cubeMX es ir al SPI Configuration y en la pestaña del DMA elegir SPI2_TX, incremento automatico de memoria y alineacion de 8 bits:


* DMA_SPI_Cube.png
(21.21 kB, 656x621 - visto 404 veces)


Luego, parte del código que vimos arriba se debe ejecutar antes de la transmision por DMA y otra parte se debe ejecutar cuando la transmision termine, por lo que necesitamos la interrupcion de transmicion terminada. La habilitamos y le damos mayor prioridad que el timer, ya que se puede dar que dicha interrupcion se produzca mientras estamos atendiendo al timer:


* DMA_IRQ.png
(30.71 kB, 651x662 - visto 370 veces)


Bien, finalmente cambiamos levemente el código para usar el DMA, y aquí es lo sorprendente. Con las HAL basta cambiar HAL_SPI_Transmit por HAL_SPI_Transmit_DMA y listo!!!!
Para la interrupcion por fin de transferencia las HAL nos dan un callback a la funcion HAL_SPI_TxCpltCallback en donde pondremos el código a ejecutar cuando la transferencia culmine:

Código: C
  1. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
  2. {
  3.         // Pongo las salidas de los TLC en 0
  4.         HAL_GPIO_WritePin(BLK_GPIO_Port, BLK_Pin, GPIO_PIN_SET);
  5.  
  6.         //Hago el barrido vertical. Tomo el Puerto A y pongo en 0 los cuatro LSB (A, B, C, D)
  7.         A_GPIO_Port->ODR &= ~(A_GPIO_Port->IDR & 0x000F);
  8.         //Luego meto el valor de la fila actual en los 4 LSB
  9.         A_GPIO_Port->ODR |= (15-ui8FilaActual);
  10.  
  11.         //Envío los datos
  12.         HAL_SPI_Transmit_DMA(&hspi2, mem_DMA[ui8FilaActual], BYTES_X);
  13. }
  14.  
  15. void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
  16. {
  17.         //una vez enviados los datos hago un latch
  18.         HAL_GPIO_WritePin(LAT_GPIO_Port, LAT_Pin, GPIO_PIN_SET);
  19.         HAL_GPIO_WritePin(LAT_GPIO_Port, LAT_Pin, GPIO_PIN_RESET);
  20.  
  21.         // Pongo las salidas de los TLC en el valor deseado
  22.         HAL_GPIO_WritePin(BLK_GPIO_Port, BLK_Pin, GPIO_PIN_RESET);
  23.  
  24.         // Incremento el índice de fila actual
  25.         ui8FilaActual++;
  26.         // Cuando llego a la fila 16 vuelvo a 0
  27.         if(ui8FilaActual == 16)
  28.                 ui8FilaActual=0;
  29. }

Con estos cambios y ejecutando denuevo tenemos la transferencia de este modo:


* CON_DMA.png
(45.98 kB, 1365x292 - visto 399 veces)


Con la sola implementacion del DMA mejoramos 3 veces la tasa de transferencia.

Saludos!
-
Leonardo Garberoglio

Desconectado KILLERJC

  • Colaborador
  • DsPIC33
  • *****
  • Mensajes: 8242
Re:Otra vez el DMA para el SPI
« Respuesta #1 en: 07 de Mayo de 2017, 16:38:25 »
Es lindo ver el DMA en accion, lo voy a ver algun dia cuando tenga un analizador logico :P

Preguntonta, el SPI no posee buffer ?
« Última modificación: 07 de Mayo de 2017, 16:59:00 por KILLERJC »

Desconectado Snaut

  • PIC12
  • **
  • Mensajes: 57
Re:Otra vez el DMA para el SPI
« Respuesta #2 en: 10 de Mayo de 2017, 07:30:20 »
Muy bueno, gracias por compartirlo! La transferencia por DMA es algo que tengo pendiente por probar aún en los STM32 pero cuando me ponga empezaré revisando la información que has compartido. Salduos