AVR Xmega - Kann ich diesen Port-/Peripherie-Init-Code vereinfachen?

Nehmen wir an, eine Funktion, die einen Port als Parameter erhält, richtet das SPI-Modul an diesem Port ein und setzt auch die erforderlichen Pins in einen Ausgangszustand.

Die Deklaration sieht in etwa so aus:

void SPIInit(PORT_t *portname);

Innerhalb der Funktion muss ich jedoch auch SPIx verwenden, wobei x der spezifische Port ist. Wenn die Funktion beispielsweise mit PORTD aufgerufen wird, verwende ich SPID für das SPI-Modul. Gibt es eine Möglichkeit, SPIx von PORTx zu erhalten, ohne 'if'-Anweisungen zu verwenden?

Antworten (3)

Ich bin mit den XMega's nicht vertraut, aber hier ist etwas, das ich schon einmal benutzt habe. Es könnte schwierig aussehen, wenn Sie Zeiger nicht auf diese Weise verwendet haben.

Nehmen wir für dieses Beispiel an, Sie haben drei SPI-Controller, denen jeweils drei Register zugeordnet sind. Nehmen wir an, diese Namen und Adressen sind in den Header-Dateien von XMega definiert:

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

Dieses Beispiel geht davon aus, dass die Konfigurationsregister jedes SPI-Registers einem ähnlichen Adressschema folgen. Es ist möglich, dass diese Technik mit dem XMega nicht funktioniert.

  1. Erstellen Sie eine Struktur, die als Adresszeiger verwendet werden soll:

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

    Wenn die Registeradressen nicht zusammenhängend sind, müssen Sie dies in Ihrer Struktur anpassen, indem Sie nutzlose Strukturmitglieder hinzufügen:

    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. Definieren Sie Zeiger auf Ihre SPI-Registeradressen:

    #define SPI1 ((SPI_Regs_t *) SPI1_CR1)
    #define SPI2 ((SPI_Regs_t *) SPI2_CR1)
    #define SPI3 ((SPI_Regs_t *) SPI3_CR1)
    
  3. Jetzt können Sie Funktionen wie folgt erstellen:

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

Natürlich müssen Sie wahrscheinlich andere Namen wählen. Ich gehe davon aus, dass "SPI1" bereits in den XMega-Headern verwendet wird. :)

Nun stellt sich die Frage: Lohnt sich das?

Es ist etwas fraglich, ob so etwas von Anfang an eine gute Idee ist, aber ja, das kann man tun. Es beginnt mit der Annahme, dass Adressen für PORTx-Registerblöcke gleichmäßig verteilt sind, und dasselbe gilt für SPIx-Blöcke. Dies scheint wahr zu sein, wenn man sich die Datenblätter ansieht, aber seien Sie gewarnt, dass dies in keiner Weise offiziell garantiert wird.

Wie auch immer, ausgehend von dieser Annahme können Sie Folgendes tun:

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 = ...;

Bei einigen Makros können Sie den Präprozessor all diese Arithmetik erledigen lassen, was besser wäre. Dennoch prüft dieser Codeabschnitt nicht, ob die resultierende Adresse wirklich gültig ist und ob ein Peripheriemodul vorhanden ist. Und Sie können diesen Trick nicht verwenden, um beispielsweise die ISR-Vektornummer zu erhalten.

Andersherum zu berechnen - von der SPI-Moduladresse zur PORT-Adresse wäre geringfügig sicherer, da für ein gültiges SPI-Modul immer der entsprechende Port vorhanden ist. #ifdefBeachten Sie auch, dass, um den obigen Code universeller zu machen, es sowieso mindestens eines erforderlich wäre - um zu prüfen, ob SPIDes definiert ist (XMEGA-E hat nur ein SPI-Modul).

Wie ich mich an vergangene Tage bei der Arbeit mit der AVR-Familie erinnere, gibt es keine Möglichkeit, das zu tun, was Sie wollen, ohne eine bedingte Auswahl.

Am direktesten ist die Verwendung der "if / else if / ..... / else"-Struktur.

Eine Alternative besteht darin, einige Aufzählungen zu erstellen, deren Elemente ähnlich wie Ihre PORT- und SPI-Peripherieblocknamen benannt sind. Wenn Sie dann ein Argument an die Konfigurations-Subroutine übergeben, übergeben Sie stattdessen den Enumerationswert if port und verwenden Sie dann eine switch-Anweisung innerhalb der Subroutine, um bestimmte SPI-Port-Konfigurationen auszuwählen.