Kampis Elektroecke

XMega – I/O

XMega Blockdiagramm

Auch im Bereich der GPIOs hat sich bei den XMega-Architektur einiges getan. Daher möchte ich in diesem Teil des Tutorials die Funktionsweise des GPIO-Controllers etwas näher erörtern.

Einen Ausgang schalten:

Im ersten Beispiel soll eine LED an Pin R.0 eingeschaltet werden. Die LED ist Active Low beschaltet, sprich sie ist eingeschaltet, wenn der Pin gegen Masse schaltet.

Zuerst muss der entsprechende Pin als Ausgang beschaltet werden. Dies erfolgt über das Datenrichtungsregister (DIR-Register) des Ports. Jedes Bit in diesem Register steht für einen Pin des Ports und ein gesetztes Bit bedeutet, dass der dazu gehörige Pin als Ausgang beschaltet ist. Wird das Bit gelöschtes, so ist der Pin als Eingang beschaltet.

PORTR.DIR |= (0x01 << 0x00);

Alternativ können auch die Register DIRCLR und DIRSET genutzt werden um einen I/O als Aus-, bzw. als Eingang zu beschalten. Der Vorteil an diesen Registern ist, dass der lesende Zugriff für die Oder-Verknüpfung eingespart wird und die komplette Instruktion in einem Taktzyklus abgearbeitet ist.

PORTR.DIRSET = (0x01 << 0x00);

Der Pegel eines jeden I/Os kann dann über das OUT-Register des jeweiligen Ports gesetzt werden.

PORTR.OUT &= ~(0x01 << 0x00);

Auch hier kann der Zugriff beschleunigt werden, indem die Register OUTSET oder OUTCLR benutzt werden um einen I/O zu setzen, bzw. zu löschen.

PORTR.OUTCLR = (0x01 << 0x00);

Über das OUTTGL-, bzw. das DIRTGL-Register des Ports kann der Zustand, bzw. die Beschaltung eines I/Os in einem Taktzyklus umgeschaltet werden, indem das entsprechende Bit in dem Register gesetzt wird.

PORTR.DIRTGL = (0x01 << 0x00);
PORTR.OUTTGL = (0x01 << 0x00);

Eingänge einlesen:

Im nächsten Beispiel soll der Zustand eines Tasters über einen I/O eingelesen und die LED entsprechend des Pegels an dem I/O geschaltet werden. Der Taster befindet sich an Pin E.5 und ist ebenfalls Active Low beschaltet. Über das DIRCLR-Register von Port E wird der Pin als Eingang beschaltet:

PORTE.DIRCLR = (0x01 << 0x05);

Der Zustand der I/Os kann aus dem IN-Register des jeweiligen Ports ausgelesen werden. Über eine if-Abfrage wird dann geprüft ob der I/O gesetzt ist.

>while(1)
{
	if((PORTE.IN & (0x01 << 0x05)) >> 0x05)
	{
		PORTR.OUTSET = (0x01 << 0x00);
	}
	else
	{
		PORTR.OUTCLR = (0x01 << 0x00);
	}
}

Wenn der Taster nicht gedrückt ist, führt der Eingang einen High-Pegel, wodurch die LED deaktiviert wird, indem der Pin für die LED gesetzt wird. Sobald der Taster gedrückt wurde, führt der Eingang einen Low-Pegel und die LED wird aktiviert.

Alternativ kann der Code auch ohne if-Abfrage formuliert werden…

while(1)
{	
	PORTR.OUT |= ((PORTE.IN & (0x01 << 0x05)) >> 0x05);
}

Pin Control Register:

Jeder Pin besitzt ein PINCTRL-Register. Mit Hilfe dieses Registers können verschiedene Funktionen eines I/Os aktiviert, bzw. deaktiviert werden.

Über das PINCTRL-Registers kann z. B. ein GPIO invertiert werden. Für den Pin R.0 sieht das  folgenermaßen aus:

PORTR.DIRSET = (0x01 << 0x00);
PORTR.PIN0CTRL |= PORT_INVEN_bm;
PORTR.OUTSET = (0x01 << 0x00);

Über die PULLDOWN– oder PULLUP-Einstellung kann für einen Eingang ein interner Pull-down, bzw. Pull-up Widerstand aktiviert werden.

PORTR.PIN0CTRL = PORT_OPC_PULLDOWN_gc;

Wenn mehrere Pins eines Ports über das PINnCTRL-Register konfiguriert werden sollen, empfiehlt sich der Einsatz des MPCMASK-Register. Als erstes wird eine Maske, die die zu konfigurierenden Pins maskiert, in das Register geschrieben. Anschließend wird ein beliebiges PINnCTRL-Register der maskierten Pins beschrieben. Die Konfiguration wird nun auf alle anderen maskierten Pins übertragen. 

PORTCFG.MPCMASK = (0x01 << 0x07) | (0x01 << 0x04) | (0x01 << 0x01);
PORTC.PIN1CTRL = PORT_INVEN_bm;

Die erste Zeile maskiert die Pins 1, 4 und 7 für eine bevorstehende Änderung im PINnCTRL-Register und mit der zweiten Zeile wird die Änderung durchgeführt, indem in eins der PINnCTRL-Register der maskierten Pins eine Änderung geschrieben wird.

Clock and Event Out Register:

Die XMegas bieten auch noch die Möglichkeit die Taktfrequenz oder einen Eventchannel über den Pin 7 eines jeden Port auszugeben. Um die Taktfrequenz auf einen Pin zu legen, muss man ledeglich das CLKOUT-Bitfeld im CLKEVOUT-Register mit der Bitmaske des entsprechenden Ports beschreiben.

Wenn nun also die Taktfrequenz an C.7 ausgegeben werden soll, muss der I/O als Ausgang konfiguriert und das Bitmuster für PORTC in das CLKEVOUT-Register geschrieben werden:

PORTC.DIRSET = (0x01 << 0x07);
PORTCFG.CLKEVOUT = PORTCFG_CLKOUT_PC7_gc;

Durch das Setzen des CLKEVPIN-Bits im PORTCFG-Register kann bei Bedarf die Position des Ausgabepins von 7 auf 4 verändert werden. Zusätzlich kann auch die Taktfrequenz der RTC auf Pin 6 eines jeden Ports ausgegeben werden, indem das RTCOUT-Bit im PORTCFG-Register gesetzt wird.

Interrupts:

Zum Schluss möchte ich noch zeigen, wie die I/Os für den Interruptbetrieb konfiguriert werden können. Das Ziel soll es sein, auf einen Interrupt durch den Taster am Pin E.5 zu reagieren und die LED an Pin R.0 bei jedem Interrupt zu togglen.

Dazu werden als erstes die verwendeten Pins konfiguriert:

PORTR.DIRSET = (0x01 << 0x00);
PORTR.OUTCLR = (0x01 << 0x00);

PORTE.DIRCLR = (0x01 << 0x00);

Da der Taster gegen Masse schaltet, muss der Interrupt bei einer fallenden Flanke am I/O ausgelöst werden. Die Flankenerkennung für die Interrupts wird über die Input/sense configuration des PINnCTRL-Register eingestellt.

PORTE.PIN5CTRL |= PORT_ISC_FALLING_gc;

Jeder Port verfügt über zwei Interruptkanäle, die INT0 und INT1 genannt werden. Über die INT0MASK– und INT1MASK-Register können die Pins eines Ports einem Interruptkanal zugewiesen werden. In diesem Beispiel soll der Kanal INT0 für den Interrupt genutzt werden. Über das INTCTRL-Register muss dann noch die Priorität des Interrupts festgelegt werden. 

PORTE.INT0MASK |= (0x01 << 0x05);
PORTE.INTCTRL = PORT_INT0LVL_LO_gc;

Jetzt fehlt noch die ISR für den Interrupt. Es muss die ISR für den Interruptkanal 0 von Port E genutzt werden. In der ISR soll dann die LED an Pin R.0 getoggled werden.

ISR(PORTE_INT0_vect)
{
	PORTR.OUTTGL = (0x01 << 0x00);
}

Und zu guter letzt werden noch die Interrupts aktiviert.

PMIC.CTRL = PMIC_LOLVLEN_bm;
sei();

Anschließend kann das Programm kompiliert und gestartet werden. Bei jedem Druck auf den Taster wird nun die LED über den verwendeten Interrupt getoggled.


Hinweis:

In diesem Code ist noch keine Entprellung drin. Daher wird der Interrupt ggf. mehrere Male hintereinander aufgerufen!


Alle Codebeispiele stehen in meinem GitHub-Repository zum Download bereit.

Ein Kommentar

  1. Vielen herzlichen Dank für diese Erklärung. Es gibt ein Typo in Interrupt Teil:
    4- PORTE.DIRCLR = (0x01 << 0x00);
    ich glaube es soll so sein:
    PORTE.DIRCLR = (0x01 << 0x05);

    VG
    Asmadnar

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert