AVR Xmega: ¿puedo simplificar este código de inicio de puerto/periférico?

Supongamos una función que obtiene un puerto como parámetro, configura el módulo SPI en ese puerto y también configura los pines necesarios en un estado de salida.

La declaración se parece a esto:

void SPIInit(PORT_t *portname);

Sin embargo, dentro de la función también necesitaré usar SPIx, siendo x el puerto específico. Por ejemplo, si se llama a la función con PORTD, usaré SPID para el módulo SPI. ¿Hay alguna forma de obtener SPIx de PORTx sin usar declaraciones 'if'?

Respuestas (3)

No estoy familiarizado con los XMega, pero aquí hay algo que he usado antes. Puede parecer complicado si no ha usado punteros de esta manera.

Para este ejemplo, supongamos que tiene tres controladores SPI y cada uno tiene tres registros asociados. Supongamos que estos nombres y direcciones están definidos en los archivos de encabezado de XMega:

SPI1_CR1 0x4100
SPI1_CR2 0x4102
SPI1_CR3 0x4104

SPI2_CR1 0x4200
SPI2_CR2 0x4202
SPI2_CR3 0x4204

SPI3_CR1 0x4300
SPI3_CR2 0x4302
SPI3_CR3 0x4304

Este ejemplo asume que los registros de configuración de cada registro SPI siguen un esquema de dirección similar. Es posible que esta técnica no funcione con el XMega.

  1. Cree una estructura para usar como punteros de dirección:

    typedef struct
    {
        volatile uint16_t CR1; // assumes 16-bit registers
        volatile uint16_t CR2;
        volatile uint16_t CR3;
    } SPI_Regs_t
    

    Si las direcciones de registro no son contiguas, deberá ajustarlas en su estructura agregando miembros de estructura inútiles:

    typedef struct
    {
        volatile uint16_t CR1; // assumes 32 bits of space between addresses
        uint16_t USELESS1;
        volatile uint16_t CR2;
        uint16_t USELESS2;
        volatile uint16_t CR3;
    } SPI_Regs_t
    
  2. Defina punteros a sus direcciones de registro SPI:

    #define SPI1 ((SPI_Regs_t *) SPI1_CR1)
    #define SPI2 ((SPI_Regs_t *) SPI2_CR1)
    #define SPI3 ((SPI_Regs_t *) SPI3_CR1)
    
  3. Ahora, puedes crear funciones así:

    void SPIInit(SPI_Regs_t* SPIx)
    {
        uint16_t myvar;
    
        SPIx->Reg1 = 0xFF;
        uint16_t myvar = SPIx->Reg2;
    }
    

Por supuesto, probablemente tendrás que elegir diferentes nombres. Espero que "SPI1" ya se use en los encabezados de XMega. :)

Entonces, ahora, la pregunta es: ¿Vale la pena?

Es un poco cuestionable si algo como esto es una buena idea desde el principio, pero sí, puedes hacerlo. Comienza con la suposición de que las direcciones de los bloques de registros PORTx están espaciadas uniformemente y lo mismo ocurre con los bloques SPIx. Esto parece ser cierto al mirar las hojas de datos, pero tenga en cuenta que no está garantizado oficialmente de ninguna manera.

De todos modos, comenzando con esta suposición, puedes hacer algo como:

uint8_t n;
SPI_t *spi;

n = ((void*)portname - (void*)&PORTC) / ((void*)&PORTD - (void*)&PORTC);
spi = (SPI_t *) ((void *)&SPIC + n*((void *)&SPID - (void *)&SPIC));
...
spi->DATA = ...;

Con algunas macros, puede dejar que el preprocesador se encargue de toda esta aritmética, lo que sería mejor. Aún así, este fragmento de código no comprueba que la dirección resultante sea realmente válida y que el módulo periférico existente. Y no puede usar este truco para obtener, por ejemplo, el número de vector ISR.

Calcularlo al revés: desde la dirección del módulo SPI hasta la dirección del PUERTO sería marginalmente más seguro ya que para el módulo SPI válido siempre hay un puerto respectivo. Tenga en cuenta también que para hacer que el código anterior sea más universal, necesitaría al menos uno #ifdefde todos modos, para verificar si SPIDestá definido (XMEGA-E tiene solo un módulo SPI).

Como recuerdo de los días pasados ​​trabajando con la familia AVR, no hay forma de hacer lo que quieres sin alguna selección condicional.

La más directa es usar la estructura "if / else if / ..... / else".

Una alternativa es crear algunas enumeraciones que tengan elementos con nombres similares a los nombres de los bloques periféricos PORT y SPI. Luego, cuando pase un argumento a la subrutina de configuración, pase el valor de enumeración si el puerto en su lugar y luego use una declaración de cambio dentro de la subrutina para seleccionar configuraciones de puerto SPI específicas.