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):
Unter Verwendung von PORTB für den Rest dieses Beitrags GPIOB
sollte die Definition somit auf das TRISB-Register (mit der Basisadresse 0xBF886040) zeigen, oder besser gesagt, GPIOB
auf 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?
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.
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.
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.
Erik Friesen