1.1x STM32 GPIO Ports
Hier eine etwas vereinfachte Betrachtung, für Details Doku lesen: [Referenz Manual rm0038 S183ff.] [Datenblatt stm32l152re S92] [STM32-API]
Prinzip-Schaltbild für einen I/O-Pin
Veranschaulichung I/O Pin Teilschaltung mit Digital
Einstieg in die Vielfalt der Einstellmöglichkeiten. Jeder Pin kann als General Purpose Input/Output (Allzweckeingabe/-ausgabe) oder mit einer alternativen Funktion verwendet werden. Im Vergleich mit dem oberen Blockschaltbild fehlen in dieser Betrachtung IDR, ODR als Register, Schmitt-Trigger, Analogsignalverarbeitung.
Von Arduino I/O-Funktionen zu direkter Hardware-Register Verwendung
16 I/O-Pins werden zu einem Port {GPIOA,GPIOB…} zusammengefasst. Hier werden Pin PB1, PB0 von GPIOB und PC1, PC0 von GPIOC betrachtet. Soll ein Pin (Anschluss) an einem µC kann als Eingang oder als Ausgang verwendet werden gibt es dabei viele Einstellmöglichkeiten. Bevor ein Pin genutzt werden kann wird er bei der Initialisierung konfiguriert.
Zur Verdeutlichung wird ein Testprogramm von Arduino-Befehlen auf direkte GPIO-Registerverwendung umgebaut.
Info: pinMode() stellt die Hardware-Register ein:
- Die Clock für den Port einschalten: RCC->AHBENR
- Port-Mode für den Pin: MODER, PUPDR, OTYPER
- Ausgabetreiber-Geschwindigkeit: OSPEEDR auf Maximum = 3
void setup(){
pinMode(PB0,INPUT_PULLDOWN);
pinMode(PB1,INPUT_PULLDOWN);
pinMode(PC0,OUTPUT);
pinMode(PC1,OUTPUT);
}
void loop(){
int a;
a = digitalRead(PB0);
digitalWrite(PC0,a);
a = digitalRead(PB1);
digitalWrite(PC1,a);
}
Wofür könnten die Pins PB1, PB0, PC1 und PC0 verwendet werden?
Wie die meisten Pins können PB1, PB0, PC1 und PC0 als digitaler Eingang oder Ausgang verwendet werden. In der Nucleo L152RE Pinbelegung finden sich weitere alternative Funktionen:
Pin | Analog | Timer | Beschreibung |
---|---|---|---|
PB0 | ADC_IN8 | TIM3_CH3 | AD-Wandler Eingang IN8, Digitaler Ausgang Timer3 Kanal 3 |
PB1 | ADC_IN9 | TIM3_CH4 | AD-Wandler Eingang IN9, Digitaler Ausgang Timer3 Kanal 4 |
PC0 | ADC_IN10 | AD-Wandler Eingang IN10 | |
PC1 | ADC_IN11 | AD-Wandler Eingang IN11 |
PB1 und PB0 als digitale Eingänge mit internem PullDown-Widerstand einstellen
An den Eingängen PB1 und PB0 sind beispielsweise Schalter (DIP-switch) gegen VCC angeschlossen, die wir abfragen möchten. Damit bei geöffneten Schaltern der Eingang nicht im “Floating” ist schalten wir die internen PullDown-Widerstände des µC ein. Mit der Arduino-Funktion pinMode(PB1,INPUT_PULLDOWN) wird dies mühelos für PB1 und entsprechend für PB0 eingestellt. Aber was passiert dabei im Hintergrund, welche Hardwareeinstellungen werden dabei genau getätigt, wie könnte dies ohne pinMode() erreicht werden?
Nichts geht ohne Clock! RCC->AHBENR
Die Register in den Ports werden durch ein Clock-Signal gesteuert, das erst in einem Register AHB peripheral clock enable register (RCC_AHBENR) eingeschaltet werden muss [Referenz Manual rm0038 S153ff.] Ohne pinMode() muss dafür diese Zeile vor allen anderen GPIOB-> eingefügt werden:
RCC->AHBENR |= (1<<1); // Enable the GPIOB clock
Mode Register MODER
Im Mode Register MODER können für jeden Pin mit 2 Bits diese 4 Modi eingestellt werden:
- 00: Input (Reset Status)
- 01: GPIO (ODR-Ausgabe)
- 10: Alternative Funktion
- 11: Analog Modus
Nach dem Reset sind die Bits 0 also alle Pins erst mal Eingänge hier muss nichts geändert werden.
PullUp/PullDown Register PUPDR
Im Blockschaltbild ist ein Register für die internen PullUP/PullDown-Widerstände zu sehen: PUPDR PullUP/PullDown Register. In diesem Register werden 32 Bit genutzt, jeweils 2 Bits pro Port-Pin, es können 3 mögliche Kombinationen eingestellt werden:
- 00: No PullUp, PullDown
- 01: PullUp
- 10: PullDown
Mit INPUT_PULLDOWN wird für PB0 0b10 eingestellt. Für PB1 muss dafür an Bit 3 eine 1 und an Bit 2 eine 0 eingestellt werden. Wir könnten das so programmieren:
GPIOB->PUPDR |= 0b1010; // oder
GPIOB->PUPDR |= 0xA; // oder
GPIOB->PUPDR |= (0b10 << 2) | (0b10 << 0); // oder
GPIOB->PUPDR |= (0b10 << 1*2) | (0b10 << 0*2);
Allgemein für PBn: (0b10 << n*2).
PC1 und PC0 als Ausgänge einstellen
Die Clock für GPIOC aktivieren:
RCC->AHBENR |= (1<<2); // Enable the GPIOC clock
Im MODER für die Pins 0b01 einstellen:
GPIOC->MODER |= (0b01 << 1*2) | (0b01 << 0*2); // 0b0101, 0b5
void setup(){
RCC->AHBENR |= (1<<1); // Enable the GPIOB clock
GPIOB->PUPDR |= 0b1010; // PB1 und PB0 PullDown an
RCC->AHBENR |= (1<<2); // Enable the GPIOC clock
GPIOC->MODER |= 0b0101; // PC1 und PC0 als Ausgaenge
}
Einlesen von Pin-Zuständen
Mit a = digitalRead(PB0) wird der Zustand des PB0-Pins eingelesen und 0 oder 1 ausgegeben. Über GPIOB->IDR können alle Pin-Zustände der GPIOB-Pins auf einmal eingelesen werden, es müsste nur noch eine Maske erstellt werden, die nur PB0 durchlässt:
a = GPIOB->IDR & 0b1; Der Ausdruck liefert 0 oder 1.
Die Maske für PB1 ist 0b10. GPIOB->IDR & 0b10 würde 0 oder 2 ergeben. Soll wie bei digitalRead() nur 0 und 1 zurück gegeben werden könnte mit Rechtsschieben das Ziel erreicht werden:
a = (GPIOB->IDR & 0b10)>>1; Oder allgemein für Pin n: (GPIOB->IDR & (1<<n))>>n
Wird der Ausdruck in einer if-Anweisung ausgewertet ist der Rechtsshift unnötig, da in C Werte ==0 false und Werte !=0 true ergeben.
Ausgeben von Pin-Werten
Mit digitalWrite(PC0,a) wird PC0 auf 0 oder 1 gesetzt. Mit diesem Code geht es auch direkter.
if(GPIOB->IDR & 0b1){ // digitalWrite(PC0,digitalRead(PB0))
GPIOC->ODR |= 0b1; // setze Bit 0 auf 1
} else{
GPIOC->ODR &= ~0b1; // setze Bit 0 auf 0
}
if(GPIOB->IDR & 0b10){ // digitalWrite(PC1,digitalRead(PB1))
GPIOC->ODR |= 0b10; // setze Bit 1 auf 1
} else{
GPIOC->ODR &= ~0b10; // setze Bit 1 auf 0
}
// Geht auch als Einzeiler
GPIOC->ODR = (GPIOC->ODR & ~0b11) | (GPIOB->IDR & 0b11);
Vergleich Arduino-Funktionen mit direkten Registerzugriffen
Arduino Funktionen
void setup(){
pinMode(PB0,INPUT_PULLDOWN);
pinMode(PB1,INPUT_PULLDOWN);
pinMode(PC0,OUTPUT);
pinMode(PC1,OUTPUT);
}
void loop(){
digtalWrite(PC0,digitalRead(PB0));
digtalWrite(PC1,digitalRead(PB1));
}
Hardwareregister verwenden
void setup(){
RCC->AHBENR |= (1<<1); // Enable the GPIOB clock
GPIOB->PUPDR |= 0b1010; // PB1 und PB0 PullDown an
RCC->AHBENR |= (1<<2); // Enable the GPIOC clock
GPIOC->MODER |= 0b0101; // PC1 und PC0 als Ausgaenge
}
void loop(){
GPIOC->ODR = (GPIOC->ODR & ~0b11) | (GPIOB->IDR & 0b11);
}
ODR-Bits setzen und rücksetzen mit BSRR
Zum einfachen Verändern von Bits im ODR ohne dass das ODR erst geladen verändert und wieder gespeichert werden muss gibt es das BSRR. Schreiben von 1en in die unteren 16 Bit bewirken ein Setzen und in die obern 16 Bit ein Rücksetzen des ODR-Bits. Falls beides gleichzeitig gemacht werden sollte hat Setzen Priorität.
Beispiele:
- PC0 setzen: GPIOC->BSRR = 0b1;
- PC1 setzen: GPIOC->BSRR = 0b10;
- PC0 rücksetzen: GPIOC->BSRR = 0b1<<16;
- PC1 rücksetzen: GPIOC->BSRR = 0b10<<16;
- PC1 und PC0 setzen: GPIOC->BSRR = 0b11;
- PC1 setzen und PC0 rücksetzen: GPIOC->BSRR = 0b10 | (0b1 << 16);
if(GPIOB->IDR & 0b1){ // digitalWrite(PC0,a)
GPIOC->BSRR = 0b1; // setze Bit 0 auf 1
} else{
GPIOC->BSRR = 0b1<<16; // setze Bit 0 auf 0
}
if(GPIOB->IDR & 0b10){ // digitalWrite(PC1,a)
GPIOC->BSRR = 0b10; // setze Bit 1 auf 1
} else{
GPIOC->BSRR = 0b10<<16; // setze Bit 1 auf 0
}
Alle GPIO Register
Name | Beschreibung | Subadresse | Zuordnung |
---|---|---|---|
MODER | Mode Register Die Funktion des Pins einstellen | 0x00 | 00: Input (Reset Status) 01: GPIO (ODR-Ausgabe) 10: Alternative Funktion 11: Analog Modus |
OTYPER | Output Type Register Ausgabemodus des Pins | 0x04 | 0: Output push-pull 1: Output open-drain |
OSPEEDR | Output Speed Register Ausgabegeschwindigkeit schneller braucht mehr Strom | 0x08 | 00: Low speed 01: Medium speed 10: High speed 11: Very high speed |
PUPDR | PullUp PullDown Register Eingebaute Widerstände einschalten | 0x0C | 00: No PullUp, PullDown 01: PullUp 10: PullDown 11: Reserviert |
IDR | Input Data Register Eingänge lesen | 0x10 | |
ODR | Output Data Register Ausgangswerte | 0x14 | |
BSRR | Bit Set/Reset Register Einzelne Bits an- bzw. ausschalten | 0x18 | 0..15 Setzen (hat Priorität) 16..31 Rücksetzen |
LCKR | Configuration Lock Register Einstellung der Pins sperren | 0x1c |
pinMode setzt den Clock und OSPEEDR auf 0b11 für den Port bei OUTPUT
Mit Oszi testen was OSPEEDR bewirkt
void setup(){ // Einmalige Ausführung => Initialisierungen...
uint test = (1<<10); // Speed 0,1,2,3 einstellen
Serial.begin(115200);
RCC->AHBENR |= (1<<0); // Enable the GPIOA clock
GPIOA->MODER |= (1<<10); // PA5 als Ausgang
GPIOA->OSPEEDR |= test;
Serial.printf("test: %4x\n",test);
Serial.printf("GPIOA->OSPEEDR: %4x\n",GPIOA->OSPEEDR);
}
void loop(){
//Serial.printf("GPIOC->OSPEEDR: %4x\n",GPIOC->OSPEEDR);
while(1){
GPIOA->BSRR = (1<<5); // PA5 <-1 mit BSRR können Bits gesetzt werden
delay(1);
GPIOA->BSRR = (1<<16+5); // PA5 <-0 die oberen 16Bit sind fürs Rücksetzen
delay(1);
}
}