PIC32 GPIO-Pins Frage zur Hardware-Abstraktionsschicht

Ich versuche, einen Hardware Abstraction Layer (HAL) für einen PIC32 (genauer gesagt PIC32MX664F128H) zu schreiben, in dem ich generische Definitionen für die betreffenden Ports verwenden kann, ohne die genauen Portnamen und Portregister verwenden zu müssen. Idealerweise möchte ich diese Definitionen nur für die verschiedenen Pins auf dem PIC verwenden und ein "Wrapper" -Modul haben, in dem ich angeben kann, auf welche Pins sich die Definitionen beziehen.

Ich habe mir diesen Forumsbeitrag durchgelesen , der im Grunde genau erklärt, was ich zu erreichen versuche. Beitrag #4 (Antwort #3) ist hier von besonderem Interesse. Die Grundidee besteht darin, eine Struktur ( GPIO_TypeDef) zu verwenden, um einen Typ zu definieren, der die 4 Register (TRIS, PORT, LAT und ODC) für jeden Port enthält (beachten Sie, dass der Forumsbeitrag für einen PIC24 (16-Bit-Prozessor) war, und das Ich habe den Code für den PIC32 (32-Bit-Prozessor) angepasst:

typedef struct {
 volatile unsigned int TRIS; //direction register - offset 0x0000
 volatile unsigned int PORT; //input data register
 volatile unsigned int LAT;  //output data register
 volatile unsigned int ODC;  //open drain register
} GPIO_TypeDef;    //gpio type definitions

Der gesamte Port wird dann gecastet und an ein Define gebunden, das verwendet wird, um auf die Portadresse im Speicher zu zeigen:

#define GPIOA  ((GPIO_TypeDef *) &TRISA)
#define GPIOB  ((GPIO_TypeDef *) &TRISB)
etc...

Die Adresse des TRIS-Registers wird für die Zeigerbindung verwendet, da es das erste der vier Register ist, das im Speicher für jeden Port auftritt. Dies ist aus dem Datenblatt des PIC ersichtlich (am Beispiel von PORTB):

PORTB-Registerkarte

Unter Verwendung von PORTB für den Rest dieses Beitrags GPIOBsollte die Definition somit auf das TRISB-Register (mit der Basisadresse 0xBF886040) zeigen, oder besser gesagt, GPIOBauf die Adresse des ersten Elements des TRISB-Registers zeigen.

Anschließend wird eine Reihe von Makros für die Hafenoperationen definiert:

#define PIN_SET(port, pins) port->LAT |= (pins) //set pins on port
#define PIN_CLR(port, pins) port->LAT &=~(pins) //clear pins on port
#define PIN_FLP(port, pins) port->LAT ^= (pins) //flip pins on port
#define PIN_GET(port, pins) ((port->PORT) & (pins)) //get pins
#define PIN_OUT(port, pins) port->TRIS &=~(pins) //pins as output

Im Benutzercode würde man z. B. einen Pin definieren, der eine LED wie folgt steuert (mit PORTB statt PORTC wie im Forumsbeitrag):

#define LED_PORT  GPIOB
#define LED       (1<<4) //led on PORTB.4

Die LED kann dann wie folgt ein-/ausgeschaltet werden:

PIN_SET(LED_PORT, LED); //turn LED on
PIN_CLR(LED_PORT, LED); //turn LED off

Wenn ich jedoch den obigen Code verwende, wird der entsprechende Pin im TRISB-Register anstelle des LATB-Registers umgeschaltet. Außerdem wird beim Versuch, die LED auszuschalten, das entsprechende Bit, das im TRISB-Register gesetzt wurde, nicht auf 0 zurückgesetzt. Warum funktioniert das nicht?

Interessant ist, wenn ich für jedes der vier Portregister separate Strukturen wie folgt definiere:

typedef struct
{
    volatile unsigned int LAT;
} IO_LAT;

typedef struct
{
    volatile unsigned int PORT;
} IO_PORT;

typedef struct
{
    volatile unsigned int TRIS;
} IO_TRIS;

typedef struct
{
    volatile unsigned int ODC;
} IO_ODC;

#define IO_LATB ((IO_LAT *)&LATB)

und schalten Sie das entsprechende Bit (Bit 4) in jedem dieser Register wie folgt um:

PIN_SET(IO_LATB, LED);
PIN_CLR(IO_LATB, LED);

es funktioniert perfekt. Dasselbe gilt, wenn ich dasselbe mit den TRIS-, PORT- und ODC-Registern mache (und natürlich die Makros entsprechend modifiziere). Warum funktioniert das und nicht die erste Methode?

Wenn Sie LATSET CLR nicht verwenden und Interrupts verwenden, wird Ihr Code Probleme verursachen.

Antworten (3)

Ich denke, das eigentliche Problem könnte darin liegen, dass Sie Ihre Struktur nicht richtig angelegt haben, um die atomare Anweisung sfr jedes sfr einzuschließen. In MplabX können Sie auf eine sfr wie PORTA klicken, um zur Kopfzeile zu gelangen. Zum Beispiel bekomme ich für eine PIC32MM-Serie dies

extern volatile __LATAbits_t LATAbits __asm__ ("LATA") __attribute__((section("sfrs"), address(0xBF802630)));
extern volatile unsigned int        LATACLR __attribute__((section("sfrs"),address(0xBF802634)));
extern volatile unsigned int        LATASET __attribute__((section("sfrs"),address(0xBF802638)));
extern volatile unsigned int        LATAINV __attribute__((section("sfrs"),address(0xBF80263C)));

Um dies korrekt zu tun, sollte Ihre Struct-Definition etwa so aussehen

typedef struct {
     volatile unsigned int TRIS; //direction register - offset 0x0000
     volatile unsigned int TRISCLR;
     volatile unsigned int TRISSET;
     volatile unsigned int TRISINV;
     volatile unsigned int PORT; //input data register
     volatile unsigned int PORTCLR;
     volatile unsigned int PORTSET;
     volatile unsigned int PORTINV;
     volatile unsigned int LAT;  //output data register
     volatile unsigned int LATCLR;
     volatile unsigned int LATSET;
     volatile unsigned int LATINV;
     volatile unsigned int ODC;  //open drain register
     volatile unsigned int ODCCLR;
     volatile unsigned int ODCSET;
     volatile unsigned int ODCINV;
} GPIO_TypeDef;    //gpio type definitions

Auch als Bonusantwort, um meinen Kommentar zu erweitern, müssen Sie die atomaren Anweisungen mit dem pic32 verwenden, da sonst Cross-Interrupt-Schreibvorgänge in Latches aufgrund von Read-Modify-Schreibvorgängen beschädigt werden. Um Ihre Beispiele zu korrigieren, sollten sie so etwas wie sein

#define PIN_SET(port, pin) port->LATSET = (1UL << pin) //set pins on port
#define PIN_CLR(port, pin) port->LATCLR = (1UL << pin) //clear pins on port
#define PIN_FLP(port, pin) port->LATINV = (1UL << pin) //flip pins on port

kkrambos Antwort verdient hier eine Erwähnung, da sie Schattierungen der richtigen Antwort enthält.

Dies beantwortet meine Frage und erweitert auch, wie ich die Makros mit den entsprechenden SET-, CLR- und INV-Registern verwenden soll. Damit markiere ich dies als Antwort auf meine Frage. Vielen Dank für den Hinweis auf die möglichen Probleme aufgrund der Read-Modify-Writes. Kann ich das PIN_GET-Makro weiterhin so verwenden, wie es in meiner Frage definiert ist? Da es keine Änderung/Schreiben eines Registers durchführt, sollte es nicht anfällig für Probleme wie bei den Makros PIN_SET, PIN_CLR und PIN_FLP sein?
Das ist richtig, Lesevorgänge an Ports sind in Ordnung, also habe ich Ihre anderen Makros in Ruhe gelassen.

Sehen Sie sich das Diagramm für einen typischen I/O-Port an.

Beachten Sie, dass WR tris alle anderen Schreibvorgänge überschreibt, sodass Sie den Port in der richtigen Reihenfolge steuern müssen. WR TRIS sollte zuerst sein, dann WR LAT, dann WR ODC. WR LAT und WR PORT gehen zu einem ODER-Gatter, sodass einer der beiden Daten auf den Pin legt.

Es funktioniert manuell, weil Sie die Reihenfolge implizit definieren, aber als globaler Befehl treten die Dinge möglicherweise nicht in der richtigen Reihenfolge auf, insbesondere wenn Sie einen Umschalteffekt erhalten.

Erwägen Sie, Ihre Typdefinition so umzuformulieren, dass sie eine bestimmte Reihenfolge enthält, damit die Daten nicht zu sich selbst zurückkehren (toggle). Als Teil des HAL muss es auch die richtige Sequenz enthalten.

Geben Sie hier die Bildbeschreibung ein

Dies bietet einen wertvollen und interessanten Einblick in den Hafenbetrieb im Allgemeinen. Vielen Dank für Ihre Antwort.

Ich kenne diesen Mikrocontroller nicht. Aber die virtuellen Adressen in dieser Tabelle (6040, 6050, 6060 und 6070) scheinen 16 Byte voneinander entfernt zu sein. Wenn ein unsigned int 32-Bit ist, platziert Ihre Struktur sie nur 4 Bytes voneinander entfernt.

Funktioniert Ihre erste Methode, wenn Sie die folgende Strukturdefinition verwenden?

typedef struct {
 volatile unsigned int TRIS; //direction register - offset 0x0000
 volatile unsigned int reserved1[3];
 volatile unsigned int PORT; //input data register
 volatile unsigned int reserved2[3];
 volatile unsigned int LAT;  //output data register
 volatile unsigned int reserved3[3];
 volatile unsigned int ODC;  //open drain register
} GPIO_TypeDef;    //gpio type definitions

Wenn dies der Fall ist, werden möglicherweise alle Bytes von 6044 bis 604F in das TRIS-Register aliasiert, und deshalb haben Sie gesehen, wie sich das TRIS-Register geändert hat, als Sie versehentlich 6048 bis 604B eingestellt haben.

Ich habe völlig übersehen, dass die virtuellen Adressen tatsächlich 16 Bytes voneinander entfernt sind. Es scheint, dass ich tatsächlich die SET-, CLR- und INV-Register für jedes Portregister vernachlässigt habe. Danke für den Hinweis auf meinen Fehler.