PSoC 5LP DMA Periferico a Periferico Ej. 1

Que tal, en este post vamos a utilizar el DMA que tiene el PSoC 5LP para enviar datos del resultado de la conversión del ADC al PWM, en especifico al valor cmp. Podemos ver el resultado del proyecto en el siguiente video:

Podemos ver que sirve perfectamente, excepto cuando llegamos a ambos extremos del potenciometro, la razón la veremos al final del post.
Primero creamos un nuevo proyecto en PSoC Creator y le damos el nombre que queramos. Colocamos uno a uno los componentes, los componentes que utilizamos es:
  • ADC Delta - Sigma
  • Clock
  • Pin analogico
  • Pin Digital de salida
  • PWM
  • DMA
Los conectamos de la siguiente forma:
Esquematico
La configuración de cada componente la vemos a continuación:
El ADC esta configurado con una resolución de 8bits, de forma continua, con el sample rate mas bajo, aqui no nos interesa la velocidad, el rango de entrada esta entre Vssa y Vdda, en el caso de la tarjeta 059 Vdda esta conectado a 5V.
Configuración ADC
El PWM esta configurado para ser implementado en UDBs, con una salida, resolucion de 8bits, el periodo al maximo y el valor del cmp es el que vamos a controlar con el potenciometro, asi que este valor de inicio no importa.
El reloj de entrada lo ponemos a 1MHz para que la frecuencia de la señal que entra al LED sea mayor a 60Hz y el ojo no note el parpadeo.
Configuración PWM
El DMA esta configurado para ser disparado con un flanco de subida en la terminal drq, esta señal la vamos a obtener de la salida EoC (End of Convertion / final de conversion) del ADC.
Configuración DMA
Ya terminada la configuración generamos las API:
Generamos las API
Una vez generadas vamos a utilizar el DMA Wizard, que es un asistente de configuración del DMA. Lo podemos encontrar en Tools -> DMA Wizard.
En la primer ventana que nos aparece nos indica el nombre del proyecto y el nombre de la instancia del DMA en nuestro esquematico, solo tenemos uno en el nuestro (en caso de tener mas tenemos que elegirlo de la lista que nos aparece.) Clic en Next.
DMA Wizard
En la segunda ventana vemos la configuración global del DMA:
  1. En esta parte configuramos la fuente de los datos, el Wizard detecta que tenemos el ADC en nuestro esquematico.
  2. En esta parte configuramos el destino de nuestros datos. Aqui notamos una de deficiencias del Wizard, esto es que no detecta todos los perifericos, pero esto lo podemos arreglar mas adelante.
  3. Aqui configuramos el número de bytes por rafaga y si cada rafaga requiere un trigger en la terminal drq del DMA.
  4. Por ultimo configuramos el numero de TDs y como se van a comportar.
DMA Wizard - Global Settings por default.
Para nuestro proyecto lo configuramos de la siguiente forma:
  1. Aqui lo dejamos como estaba, el Wizard detecto nuestro ADC.
  2. Tambien lo dejamos como estaba, lo cambiaremos despues.
  3. Habilitamos la configuracion manual, cada transferencia es de 1 byte y cada rafaga requiere de un trigger.
  4. Solo tendremos un TD y se va a ejecutar en loop.
Damos clic en Next.
DMA Wizard - Global Settings de nuestro proyecto.
Ahora nos aparece la ventana para configurar los TDs, podemos ver que el Wizard automaticamente coloco el registro del ADC de donde obtendra los datos.Aqui tambien podemos cambiar el endianess (esto solo se habilita en el PSoC 3), tambien podemos habilitar la terminación por hardware request (trq), habilitacion para generar un pulso al terminal la transferencia (nrq), la cantidad de bytes para transferir por el TD (Length), la fuente de los datos (source), la habilitacion de incrementar la direccion de la fuente de datos (esto sirve para transferir datos de un array), configuracion del destino de los datos (Destination), la habilitacion de incrementar la direccion del destino de datos (esto sirve para transferir datos hacia un array), habilitacion de ejecutar el siguiente TD en la cadena cuando termina el anterior sin esperar un trigger (Auto Next) y el siguiente TD en la cadena (Next TD).
DMA Wizard - Transaction Descriptors por default.
En nuestro caso, Length lo configuramos con 0 para que la transferencia sea "infinita", la fuente la dejamos como esta, es la correcta, en Destination colocamos una palabra que podamos encontrar facilmente despues para cambiarla por el valor correcto, deshabilitamos los Inc ya que no queremos incrementar el destino de la fuente o destino, y como es un loop el Next TD será 0 (podemos ver el número de los TDs en el extremo izquierdo).
Clic en Next.
DMA Wizard - Transaction Descriptors de nuestro proyecto.
Al final nos aparece el código de configuración del DMA que debemos agregar en nuestro proyecto.
DMA Wizard - Generated Code.
Lo copiamos y pegamos en nuestro main.c, yo prefiero colocarlo en una función.
Tenemos que hacer algunos cambios en el código generado, estos cambios son:
  •  #define DMA_DST_BASE (CYDEV_PERIPH_BASE) ----> linea 7
Aqui tenia SRAM_BASE, este valor se ocupa al tener el destino en SRAM, en nuestro caso el destino es otro periferico, asi que ocupamos PERIPH_BASE.
  • linea 40, cambiamos la palabra testPWM por el registro donde se escribe el valor del cmp.
Podemos saber el registro entrando al código generado por PSoC Creator, en la función PWM_WriteCompare podemos ver el registro al que se le escribe el valor, este es PWM_COMPARE1_LSB_PTR.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <project.h>

/* Defines for DMA */
#define DMA_BYTES_PER_BURST 1
#define DMA_REQUEST_PER_BURST 1
#define DMA_SRC_BASE (CYDEV_PERIPH_BASE)
#define DMA_DST_BASE (CYDEV_PERIPH_BASE)

void DMA_Config(void);

int main(){
    
    CyGlobalIntEnable;

    PWM_Start();
    ADC_Start();
    
    DMA_Config();
    
    ADC_StartConvert();

    for(;;){
    }
}

void DMA_Config(){

    /* Variable declarations for DMA */
    /* Move these variable declarations to the top of the function */
    uint8 DMA_Chan;
    uint8 DMA_TD[1];

    /* DMA Configuration for DMA */
    DMA_Chan = DMA_DmaInitialize(DMA_BYTES_PER_BURST,
                                    DMA_REQUEST_PER_BURST,
                                    HI16(DMA_SRC_BASE),
                                    HI16(DMA_DST_BASE));
    DMA_TD[0] = CyDmaTdAllocate();
    CyDmaTdSetConfiguration(DMA_TD[0], 0, DMA_TD[0], 0);
    CyDmaTdSetAddress(DMA_TD[0], LO16((uint32)ADC_DEC_SAMP_PTR), LO16((uint32)PWM_COMPARE1_LSB_PTR));
    CyDmaChSetInitialTd(DMA_Chan, DMA_TD[0]);
    CyDmaChEnable(DMA_Chan, 1);
}

/* [] END OF FILE */
Compilamos, programamos nuestro PSoCy tenemos listo el proyecto :D.

EXTRA:
Ahora me toca ver por que cuando giro el potenciometro hasta el inicio o al final el LED se prende y apaga respectivamente. Se me ocurrio que podia agreguar un isr en la salida del DMA para poder ver los contenidos de los registros del ADC y PWM al terminar la transferencia.
Agregamos el componente isr al esquematico.
A continuación generemos las API, y en el main cambiamos la configuración del DMA, los cambios estan en la linea 14, cambiamos el 0 por un 1, esto le indica al DMA que despues de transferir 1 byte se va a pasar al siguiente TD, en nuestro caso es el mismo TD, y el último parametro DMA__TD_TERMOUT_EN es el que habilita un pulso en la terminal nrq para disparar la interrupción:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
void DMA_Config(){

    /* Variable declarations for DMA */
    /* Move these variable declarations to the top of the function */
    uint8 DMA_Chan;
    uint8 DMA_TD[1];

    /* DMA Configuration for DMA */
    DMA_Chan = DMA_DmaInitialize(DMA_BYTES_PER_BURST,
                                    DMA_REQUEST_PER_BURST,
                                    HI16(DMA_SRC_BASE),
                                    HI16(DMA_DST_BASE));
    DMA_TD[0] = CyDmaTdAllocate();
    CyDmaTdSetConfiguration(DMA_TD[0], 1, DMA_TD[0], DMA__TD_TERMOUT_EN);
    CyDmaTdSetAddress(DMA_TD[0], LO16((uint32)ADC_DEC_SAMP_PTR), LO16((uint32)PWM_COMPARE1_LSB_PTR));
    CyDmaChSetInitialTd(DMA_Chan, DMA_TD[0]);
    CyDmaChEnable(DMA_Chan, 1);
}

Ahora utilizamos los apicallbacks para agregar la función que se ejecuta en la interrupción del DMA en nuestro main.c, además agregamos una variable que se va a cambiar en la interrupción del DMA, tenemos que declararla como global y volatil. Tuve que cambiar las optimizaciones del compilador a None.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <project.h>
#include "cyapicallbacks.h"

/* Defines for DMA */
#define DMA_BYTES_PER_BURST 1
#define DMA_REQUEST_PER_BURST 1
#define DMA_SRC_BASE (CYDEV_PERIPH_BASE)
#define DMA_DST_BASE (CYDEV_PERIPH_BASE)

volatile uint8_t foo;

void DMA_Config(void);

int main(){
    
    CyGlobalIntEnable;

    PWM_Start();
    ADC_Start();
    isr_Start();
    
    DMA_Config();
    
    ADC_StartConvert();

    for(;;){
    }
}

void DMA_Config(){

    /* Variable declarations for DMA */
    /* Move these variable declarations to the top of the function */
    uint8 DMA_Chan;
    uint8 DMA_TD[1];

    /* DMA Configuration for DMA */
    DMA_Chan = DMA_DmaInitialize(DMA_BYTES_PER_BURST,
                                    DMA_REQUEST_PER_BURST,
                                    HI16(DMA_SRC_BASE),
                                    HI16(DMA_DST_BASE));
    DMA_TD[0] = CyDmaTdAllocate();
    CyDmaTdSetConfiguration(DMA_TD[0], 1, DMA_TD[0], DMA__TD_TERMOUT_EN);
    CyDmaTdSetAddress(DMA_TD[0], LO16((uint32)ADC_DEC_SAMP_PTR), LO16((uint32)PWM_COMPARE1_LSB_PTR));
    CyDmaChSetInitialTd(DMA_Chan, DMA_TD[0]);
    CyDmaChEnable(DMA_Chan, 1);
}

void isr_Interrupt_InterruptCallback(void){
    foo ^= 1; 
}

/* [] END OF FILE */

Ahora durante la sesión de debug vamos a agregar un breakpoint en la interrupcion que genera el DMA:
Breakpoint en la interrupción del DMA
Ahora damos clic en play y la ejecución del programa se detendra en la linea 50, ahora abrimos dos ventanas para ver los registros del ADC y PWM, el registro del ADC que vamos a ver es ADC_DEC_SAMP_PTR que se encuentra en 0x40004e10 y PWM_COMPARE1_LSB_PTR que se encuentra en 0x4000642b (estos valores los encontré siguiendo la definición de los registros Ctrl+F12), podemos ver que si giro el potenciometro hasta su inicio el valor que se carga en el registro PWM_COMPARE1_LSB_PTR es 0xFF, asi que la señal pasa a ser 0 hasta el final de la cuenta, por eso siempre vemos encendido el LED.
Potenciometro hasta el inicio.
Y si giramos el potenciometro hasta el final, el valor que se carga en PWM_COMPARE1_LSB_PTR es 0x01:
Potenciometro hasta el final.
Entonces eso explica el por que el LED se prende estando el potenciometro en su inicio y esta apagado estando en el final, contrario a lo esperado.

Como siempre, dejo el proyecto en GitHub:
Indice de posts sobre DMA en PSoC aqui.

Saludos :D

Comentarios

Entradas más populares de este blog

PSoC Creator Tools

PSoC5LP Usando el SAR ADC y la señal EoS para manejar una LUT