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.
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
// Pongo las salidas de los TLC en 0
HAL_GPIO_WritePin(BLK_GPIO_Port, BLK_Pin, GPIO_PIN_SET);
//Hago el barrido vertical. Tomo el Puerto A y pongo en 0 los cuatro LSB (A, B, C, D)
A_GPIO_Port->ODR &= ~(A_GPIO_Port->IDR & 0x000F);
//Luego meto el valor de la fila actual en los 4 LSB
A_GPIO_Port->ODR |= (15-ui8FilaActual);
//Envío los datos
HAL_SPI_Transmit_DMA(&hspi2, mem_DMA[ui8FilaActual], BYTES_X);
//una vez enviados los datos hago un latch
HAL_GPIO_WritePin(LAT_GPIO_Port, LAT_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(LAT_GPIO_Port, LAT_Pin, GPIO_PIN_RESET);
// Pongo las salidas de los TLC en el valor deseado
HAL_GPIO_WritePin(BLK_GPIO_Port, BLK_Pin, GPIO_PIN_RESET);
// Incremento el índice de fila actual
ui8FilaActual++;
// Cuando llego a la fila 16 vuelvo a 0
if(ui8FilaActual == 16)
ui8FilaActual=0;
}
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:
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:
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:
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:
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:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
// Pongo las salidas de los TLC en 0
HAL_GPIO_WritePin(BLK_GPIO_Port, BLK_Pin, GPIO_PIN_SET);
//Hago el barrido vertical. Tomo el Puerto A y pongo en 0 los cuatro LSB (A, B, C, D)
A_GPIO_Port->ODR &= ~(A_GPIO_Port->IDR & 0x000F);
//Luego meto el valor de la fila actual en los 4 LSB
A_GPIO_Port->ODR |= (15-ui8FilaActual);
//Envío los datos
HAL_SPI_Transmit_DMA(&hspi2, mem_DMA[ui8FilaActual], BYTES_X);
}
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
//una vez enviados los datos hago un latch
HAL_GPIO_WritePin(LAT_GPIO_Port, LAT_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(LAT_GPIO_Port, LAT_Pin, GPIO_PIN_RESET);
// Pongo las salidas de los TLC en el valor deseado
HAL_GPIO_WritePin(BLK_GPIO_Port, BLK_Pin, GPIO_PIN_RESET);
// Incremento el índice de fila actual
ui8FilaActual++;
// Cuando llego a la fila 16 vuelvo a 0
if(ui8FilaActual == 16)
ui8FilaActual=0;
}
Con estos cambios y ejecutando denuevo tenemos la transferencia de este modo:
Con la sola implementacion del DMA mejoramos 3 veces la tasa de transferencia.
Saludos!