Bueno, pues visto que en este foro apenas se habla de la DMA de los dspic (supongo que tambien aplicable a pic32) he decidido abrir un post para comentar todo sobre este controlador que traen la mayoria de los dspics y que casi nadie usa, o habla de ello, a pesar de que una vez comprendido es bastante sencillo de usarla.
Bien, lo primero saber es que DMA es un controlador de memoria por asi decirlo, intentare explicarlo de forma sencilla con ejemplos sencillos, nada de tecnicismos, para que todo el mundo lo pueda entender. DMA significa direct access memory, osea acceso directo a memoria, pero como digo es un controlador el cual actua entre "memorias" haciendo transferencias o recepciones.
¿Y para que sirve?
-Pues sirve para hacer transferencias/recepciones entre distintos perifericos que trae el pic (SPI, USART, CAN, ADC...) y la memoria de sistema (ya sea memoria DMA o la RAM normal del pic).
¿Vale pero que diferencia hay entre usar el metodo tradicional y usar el DMA para usar los perifericos?
-En principio si vamos a usar ese periferico para transmitir pocos bytes no merece la pena usar el DMA, pero cuando queremos enviar/recibir gran cantidad de datos es muy muy util, por ejemplo enviar cada x tiempo las capturas de 10 entradas analogicas al puerto usart. El numero de bytes o words (2 bytes) esta limitado a 16KBytes o bien 16KWords (lo que serian 32Kbytes) por buffer, normalmente se pueden usar 2 buffers por lo cual seria 32kbytes y 32kwords respectivamente, todo esto depende de que tipo de transferencia queramos hacer (8bits o 16bits), todo esto lo explicare mas adelante.
-¿Alguna ventaja mas?
-Pues si, son varias mas, entre ellas esta en que hace la transferencia automaticamente sin que la CPU actue, por lo cual podemos estar transferiendo datos por usart mientras nuestro programa esta haciendo otra cosa, incluso podemos dejar que lo haga de forma continua (una vez acaba empieza de nuevo). Asi nos evitamos tener que estar haciendo un for(), o esperando interrupciones para estar enviando/recibiendo datos, por lo cual podermos hacer un codigo mas complejo sin tener que recurrir a mucha velocidad ya que nos evitamos el estar ejecutando funciones para las transmisiones/recepciones.
-¿Cuando hablaste de memoria DMA y memoria RAM normal que quisiste decir?
-Bien, pues habria que mirar si nuestro pic cuenta con memoria dma, y de cuanta memoria dispone, para esto en el datasheet suele venir, tambien en la web de microchip te viene, el pic que yo uso (dspic33ep512mu810) lleva 4Kbytes de DMA (la verdad es que es poca).
La diferencia de la memoria DMA y la RAM es que la DMA es de 2 puertos, esto quiere decir que el controlador DMA puede estar escribiendo/leyendo la memoria DMA mientras que la CPU tambien lo hace, sin que ninguno tenga que esperar a que el otro acabe. Existe excepciones, por ejemplo si el DMA quiere escribir/leer la misma direccion que la CPU hay una pequeña tabla que explica cual prevalece sobre cual, esto ya lo explicare mas adelante.
-¿Que mas deberia saber?
-Pues como dije el DMA es un controlador, este cuenta con determinados canales (el que yo uso 16 canales), normalmente se usan 2 canales por periferico (uno para enviar y otro para recibir), pero esto quiere decir que tu puedes configurar los canales que quieras para el periferico que quieras, no tienen porque ser todos, o ninguno...
Tambien saber que si usamos el controlador DMA para que use la memoria RAM normal existe unas preferencias entre varios controladores, y no todos pueden leerla/escribirla simultaneamente, primero lo haria uno (por defecto la CPU) y luego otro.. Existe un registro para cambiar las prioridades, pero todo esto lo explicare con diagramas para que sea mas sencillo de entender.
Bien, ahora mas o menos se deberia entender cual es su uso, y para que sirve.
Primero empezare explicando los registros para usarla, y los bits de configuracion. Cuando ponga una x en minuscula querra decir un numero, en este caso seria el numero de canal, por ejemplo DMAxCON, la x seria entre 0 y 15, DMA0CON=registro configuracion del canal DMA 0.
*CHEN=Canal activado/desactivado, poniendo este bit a 1 seria activo, y 0 desactivado
*SIZE=el tamaño, es lo que explique anteriormente, transferencias de 16bits o 8bits, 0=16bits; 1=8bits
*DIR=es la direccion del canal (transferencia o recepcion), 1=lectura desde la ram y escritura al periferico (transferencia), 0=lectura del periferico y escritura a la ram (recepcion)
*HALF=se genera una interrupcion al final de la transferencia/recepcion, o bien cuando lleve la mitad de los datos (0=al final), (1=a la mitad).
*NULLW=se utiliza para enviar datos nulos, mas adelante lo explico.
*AMODE= tipo de direccionamiento, esto lo explico mas detenidamente:
-11=(ni idea que haria este valor)
-10=(este modo no lo comprendo muy bien, intentare explicarlo mas adelante)
-01=Registro indirecto SIN incremento. Que quiere decir esto? Pues imaginaos que queremos enviar siempre el mismo dato, por ejemplo para el SPI enviamos un dato para poder recibir otro dato, siempre debemos escribirle algo para poder recibirlo, con este tipo de modo hariamos que el DMA envie los datos que apunten a determinada direccion de memoria durante x veces sin que el registro de memoria se incremente, por lo cual siempre apuntaria a la misma direccion y siempre enviaria el mismo dato.
-00=Registro indirecto CON incremento. Es lo contrario del anterior, es decir, imaginemos que queremos enviar un array de 16 direcciones, el array se encontraria en la direccion 0x1000 (por ejemplo), en el primer envio enviaria lo que se encuentre en la direccion 0x1000, en el segundo envio 0x1001, en el tercero 0x1002.... y asi hasta 0x0015. En el caso de "01" siempre enviaria el contenido de la direccion 0x1000 SIN INCREMENTAR.
*MODE=tipo de modo
-11=Esto funcionaria de la siguiente forma: enviaria el numero de datos que queramos, y una vez los ha enviado se desactiva automaticamente, tiene el modo ping-pong activado, como hable anteriormente se pueden enviar 2 buffers, el modo ping-pong quiere decir esto, una vez envia el buffer1 pasaria al buffer2 y una vez acabe el buffer2 se desactivaria como anteriormente dije.
-10=Enviaria continuamente, como el modo anterior este tiene el modo ping-pong activado, lo que haria seria, transferir buffer1, luego el 2, luego el 1 de nuevo, luego 2.... y asi hasta que lo desactivemos.
-01=Funcionaria como el "11" pero no tiene el modo ping-pong activado, por lo cual enviaria el buffer1, y una vez se acabe se desactiva.
-00=Funcionaria como el "10" pero sin el modo ping-pong, enviaria continuamente el buffer1 hasta que lo desactivemos.
DMAxREQ: Este registro se usa para seleccionar la IRQ, es decir a que periferico acudira el canal que elijamos, como anteriormente decia, el DMA se utiliza para la transferencia entre perifericos/memoria. Existe una lista donde te indica a que periferico concuerda cada numeracion, como no quiero confundir a la gente ya que posiblemente entre pics pueda variar os recomiendo que busqueis en el apartado DMA/registros/DMAxREQ y mireis la lista para cada pic, por ejemplo para mi pic seria:
00001010 = SPI1 – Transfer Done, si ponemos DMA0REQ=0b00001010; usariamos el canal dma0 para el SPI1
-FORCE: este bit se usa para FORZAR la transferencia sin que el periferico lo haga, muchos perifericos (SPI por ejemplo) no empiezan la transmision hasta que TU se lo ordenes, poniendo el bit FORCE a 1 haces eso, una vez hecho esto, automaticamente se sigue transmitiendo durante las veces que le indique en el registro que a continuacion pongo.
Como anteriormente os he hablado de X transferencias, este registro se usa para indicar cuantas transferencias se hara, es IMPORTANTE saber que siempre habria que poner x-1, es decir, si queremos hacer 10 transferencias tendriamos que poner 9, en caso de 1 transferencia tendriamos que poner 0 y asi siempre.
DMA0CNT=15; //hariamos 16 transferencias.
Este registro se usa para indicar la DIRECCION del PERIFERICO, es muy simple, por ejemplo para el SPI1 seria &SPI1BUF, para el USART1 RX seria &U1RXREG, para USART1 TX &U1TXREG.
Ejemplo:
DMA0PAD=&U1RXREG; //para que el DMA0 se utilice con el registro de recepcion del USART1
DMA1PAD=&U1TXREG; //para que el DMA1 se utilice con el registro de envio USART1
Bueno, este registro es muy importante saber como se usa, por lo cual deberia quedaros superclaro.
En momentos anteriores he estado hablando de "buffers", tambien dije que se podian usar 2 buffers para aumentar el numero de transferencias, digamos que estos registros son los que controlan los buffers.
Antes de nada hay que saber como funciona la asignacion de memoria, todas las variables que sean globales o estaticas se almacenan en una direccion de memoria, por ejemplo "unsigned int variable;" esto lo determina automaticamente el compilador, a no ser que nosotros se lo especifiquemos de forma manual, pero aqui no me voy a meter.
Un int ocupa 16bits, un char ocupa 8bits (no me voy a meter en variables de mayor tamaño para no confundir), y OJO es posible que variando el compilador tambien varien el tamaño de las variables, por eso es bueno saberlo cuando empezamos a programar con determinado compilador.
Bien, como decia antes cada variable se posiciona en una direccion determinada de memoria, como hablamos del DMA como transferencia de grandes cantidades de datos pues lo ideal es usar un array para almacenar todas, o bien una struct y enviarla al completo.
Por ejemplo unsigned int array[32];
Esta variable contendria 32 unsigned ints, lo que quiere decir que su tamaño seria 32*16=512BITS, si lo queremos ver por bytes, seria 32*2=64bytes.
Un ejemplo igual seria: unsigned char array[64]; //seria del mismo tamaño
Bien, pues imaginemos que el primer array empieza en la direccion 1000 de la RAM, si nosotros queremos saber la informacion que contiene la posicion 5 del array utilizariamos array[4] (recordad que empieza en el 0 y acaba en el 31). En nuestro caso al ser un unsigned int cada posicion se aumentaria en 2 direcciones, por lo cual estariamos viendo lo que hay en la direccion 1010, en el caso del unsigned char tendriamos que utilizar array[9] para ver la misma direccion.
Sabiendo esto en el DMAxSTAL y H hay que poner la direccion de memoria de nuestros buffers, para ello en C utilizamos el & para que nos diga en que posicion se encuentra esa variable. Es importante saber que el DMAxSTAL es para los 16bits de direccion de memoria baja, y DMAxSTAH para la parte alta, es decir si nuestra variable esta en la direccion 1000 la parte alta no la necesitariamos, con asignarle 0 vale. Cabe decir que dependiendo del pic quizas nunca se utilice la parte H ya que el pic no tiene tanta ram como para asignarle una direccion tan grande. Pero bien sigamos:
DMA0STAH=0;
DMA0STAL=(unsigned int)&array; //es importante poner el unsigned int delante para hacer la conversion y que no nos de un warning el compilador.
Como veis es simple, unicamente le asignamos la direccion de nuestro buffer y se acabo.
En caso de querer usar los 2 buffers, utilizariamos tambien el registro DMAxSTBH y DMAxSTBL, seria igual, lo unico que cambia es el STA por STB.
Bien, hasta aqui tenemos todos los registros, existen varios registros mas de errores para colisiones entre transferencias y cosas parecidas, pero de eso no hablare, si quereis investigar esta al final de la seccion DMA.