'Limpiar' registros de STATUS con DMA en PSoC 5LP

En ocasiones cuando queremos usar las señales generadas por interrupciones de los bloques digitales de PSoC tenemos que leer los registros de STATUS de dicha interrupción para 'limpiar' ese flag.
Esto lo podemos hacer usando interrupciones,  que dentro de su rutina no hagan nada mas que leer el registro STATUS de la interrupcion, sin embargo las interrupciones tienen latencia y usamos el CPU para atenderlas. En la imagen de abajo podemos ver como se implementaria la lectura del registro de STATUS de las interrupciones del Tx de un bloque SPI Master, la interrupción se genera al mandar un byte/word por el pin MOSI.

Usamos el componente isr para activar la interrupcion con un flanco ascendente.
y dentro de nuestro main:
 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
#include <project.h>
#include "cyapicallbacks.h"

int main()
{
    CyGlobalIntEnable;

    isr_Tx_Start();
    SPI_Start();

    CyDelay(2500);
    
    for(;;)
    {
        
        CyDelayUs(50);
        SPI_WriteTxData(0xDEUL);
        SPI_WriteTxData(0xADUL);
        SPI_WriteTxData(0xC0UL);
        SPI_WriteTxData(0xDEUL);
        SPI_ClearRxBuffer();

    }
}

void isr_Tx_Interrupt_InterruptCallback(void){
    SPI_TX_STATUS_REG; /* Leemos el registro para limpiar el flag de la interrupción */
}
Podemos ver las señales en esta captura de PulseView:
La señal TxInt tarda 750 ns en ser limpiada si usamos una interrupción.
750 ns no es mucho tiempo pero utilizamos el CPU :/, podemos hacerlo mejor haciendo uso del DMA que se encuentra en PSoC 5LP. Esto lo lei en el AN84810 PSoC 3 and PSoC 5LP advanced DMA topics.

Arrastramos un componente DMA al esquematico y lo configuramos como sigue:

Configuración del DMA
 Solo configuramos el trigger, este canal DMA se va a activar cuando tenga un flanco de subida en el pin 'drq', abajo podemos ver el esquematico completo:
Esquematico propuesto

y hacemos clic en Generate Application, con esto se generan todas las API's (funciones) de nuestros componentes omitiendo el paso de la compilación (que en ocaciones tarda mucho tiempo).
Generación de API's

Una vez terminado vamos a la pestaña Tools -> DMA Wizard, esta aplicación nos ayuda a generar el código para configurar el DMA, una desventaja del DMA Wizard es que no tiene implementados todos los registros de los perifericos, asi que al final tenemos que hacer pequeños ajustes para que el DMA se configure como queremos.
DMA Wizard
Nos aparecerá la ventana de configuración:

Proyecto e instancia del componente DMA que vamos a configurar
Nos detecta el proyecto activo y la instancia del DMA que vamos a configurar, damos clic en Next>. En la ventana que nos aparece podemos configurar:
  1. Source (fuente) de los datos que va a transferir el DMA, en este caso vemos una desventaja del Wizard, no nos detecta el SPI, asi que lo dejamos como esta por default, despues vamos a modificar el código.
  2. Destination (destino) de los datos que va a transferir el DMA, el destino de los datos será una variable ubicada en la SRAM del PSoC, asi que este parametro esta bien.
  3. El numero de bytes que va a transmitir por burst (ráfaga) y si cada burst va a necesitar un trigger para ser enviado, en este caso necesitamos transferir 1 byte y que cada byte se transmita en cada trigger
  4. El número de TD (Transaction Descriptors/Descriptores de transacción) es 1 y va a retornar a si mismo cada vez que se complete, asi que lo configuramos como loop.
Configuración del DMA
Damos clic en Next>. Nos aparecera la siguiente ventana donde seguimos con la configuración del TD:
  1.  En esta casilla podemos habilitar la generación de un pulso en la señal 'nrq' del componente, no lo necesitamos en este proyecto.
  2. Length: En este parametro configuramos la longitud de los datos, es de 1 byte, por lo tanto escribimos '1'.
  3. Source o fuente de los datos, este parametro lo vamos a cambiar mas adelante, asi que escribimos una palabra que sea facil de identificar despues.
  4. No queremos que se incremente la dirección en memoria de la fuente cada que se active el TD, asi que deshabilitamos esta casilla.
  5. Destination o destino de los datos es la dirección en memoria de una variable, no vamos a hacer nada con esta variable en el proyecto, pero seria útil si tenemos varias fuentes de interrupción y queremos saber cual fue la que activo la interrupción.
  6. Tampoco queremos que aumente la dirección en memoria del destino, asi que deshabilitamos esta casilla.
  7. EL siguiente TD será el mismo TD, que tiene el número 0, asi que dejamos este parametro como esta.
Configuración de los TDs
 Damos clic en Next>.
En la ultima ventana nos aparece el código generado, lo copiamos y damos clic en Finish.
Código generado por el DMA Wizard
 Este código lo vamos a pegar dentro de nuestro main haciendo unos cambios que dejaré en los comentarios en cada linea. Asi queda nuestro main:

 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
54
55
56
57
58
59
60
61
62
63
64
65
#include <project.h>

/* Defines for DMA_ClearSPITxInterrupt */
#define DMA_ClearSPITxInterrupt_BYTES_PER_BURST 1
#define DMA_ClearSPITxInterrupt_REQUEST_PER_BURST 1

/* Aqui cambiamos el CYDEV_SRAM_BASE por CYDEV_PERIPH_BASE ya que vamos
 * a leer un registro de un periferico. 
 */
#define DMA_ClearSPITxInterrupt_SRC_BASE (CYDEV_PERIPH_BASE)
#define DMA_ClearSPITxInterrupt_DST_BASE (CYDEV_SRAM_BASE)

/* Variable declarations for DMA_ClearSPITxInterrupt */
/* Move these variable declarations to the top of the function */
uint8 DMA_ClearSPITxInterrupt_Chan;
uint8 DMA_ClearSPITxInterrupt_TD[1];

/* Nuestra variable DUMMY, este es el destino */
volatile uint8_t Interrupt_Status;

void DMA_Config(void);

int main()
{
    CyGlobalIntEnable;

    SPI_Start();
    DMA_Config();
    
    for(;;)
    {
        
        CyDelayUs(50);
        SPI_WriteTxData(0xDEUL);
        SPI_WriteTxData(0xADUL);
        SPI_WriteTxData(0xC0UL);
        SPI_WriteTxData(0xDEUL);
        SPI_ClearRxBuffer();

    }
}

void DMA_Config(void){
    /* DMA Configuration for DMA_ClearSPITxInterrupt */
    DMA_ClearSPITxInterrupt_Chan = DMA_ClearSPITxInterrupt_DmaInitialize(
                                    DMA_ClearSPITxInterrupt_BYTES_PER_BURST,
                                    DMA_ClearSPITxInterrupt_REQUEST_PER_BURST, 
                                    HI16(DMA_ClearSPITxInterrupt_SRC_BASE),
                                    HI16(DMA_ClearSPITxInterrupt_DST_BASE));
    
    DMA_ClearSPITxInterrupt_TD[0] = CyDmaTdAllocate();
    
    CyDmaTdSetConfiguration(DMA_ClearSPITxInterrupt_TD[0], 1, DMA_ClearSPITxInterrupt_TD[0], 0);
    
    /* Leemos el Tx STATUS Register para 'limpiar' el flag de la interrupcion */
    CyDmaTdSetAddress(DMA_ClearSPITxInterrupt_TD[0],
                        LO16((uint32)&amp;SPI_TX_STATUS_REG), // Este es el registro que será nuestra fuente
                        LO16((uint32)&amp;Interrupt_Status)); // Nuestro destino
    
    CyDmaChSetInitialTd(DMA_ClearSPITxInterrupt_Chan, DMA_ClearSPITxInterrupt_TD[0]);
    
    CyDmaChEnable(DMA_ClearSPITxInterrupt_Chan, 1);
}

/* [] END OF FILE */
Y podemos ver unas capturas de la implementación funcionando:

Señal en TxInt nos indica que la interrupción se 'dispara' y 'limpia' cada que mandamos un byte por el MOSI

Con esta implementación la señal dura 250 ns y sin intervención del CPU la 'limpiamos' :D

Como siempre dejo el proyecto en los repos de GitHub.
  • Proyecto con interrupción aqui. 
  • Proyecto con DMA aqui.

Comentarios

Entradas más populares de este blog

PSoC Creator Tools

sprintf en PSoC Creator 3.0 y 3.1