1.1x STM32 GPIO Ports

MindMap zu Ports
MindMap zu Ports: Da hängt einiges dran…

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

STM32 GPIO Blockschaltbild
STM32 GPIO Blockschaltbild

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.

GPIO I/O Pin
GPIO I/O Pin

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:

PinAnalogTimerBeschreibung
PB0ADC_IN8TIM3_CH3AD-Wandler Eingang IN8, Digitaler Ausgang Timer3 Kanal 3
PB1ADC_IN9TIM3_CH4AD-Wandler Eingang IN9, Digitaler Ausgang Timer3 Kanal 4
PC0ADC_IN10AD-Wandler Eingang IN10
PC1ADC_IN11AD-Wandler Eingang IN11
Alternative Pin-Funktion

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?

DIP-Schalter
SturmBoard DIP-Schalter

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

AHB peripheral clock enable register (RCC_AHBENR)
AHB peripheral clock enable register (RCC_AHBENR)

Mode Register MODER

Im Mode Register MODER können für jeden Pin mit 2 Bits diese 4 Modi eingestellt werden:

  1. 00: Input (Reset Status)
  2. 01: GPIO (ODR-Ausgabe)
  3. 10: Alternative Funktion
  4. 11: Analog Modus

Nach dem Reset sind die Bits 0 also alle Pins erst mal Eingänge hier muss nichts geändert werden.

Mode Register
Mode Register Auszug für Pin 1 und Pin 0

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:

  1. 00: No PullUp, PullDown
  2. 01: PullUp
  3. 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); 
PullUp/PullDown Register PUPDR
PullUp/PullDown Register Auszug für Pin 1 und Pin 0

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);
BSRR Bit Set Reset Register
BSRR Bit Set Reset Register
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

NameBeschreibungSubadresseZuordnung
MODERMode Register
Die Funktion des Pins einstellen
0x0000: Input (Reset Status)
01: GPIO (ODR-Ausgabe)
10: Alternative Funktion
11: Analog Modus
OTYPEROutput Type Register
Ausgabemodus des Pins
0x040: Output push-pull
1: Output open-drain
OSPEEDROutput Speed Register
Ausgabegeschwindigkeit
schneller braucht mehr Strom
0x0800: Low speed
01: Medium speed
10: High speed
11: Very high speed
PUPDRPullUp PullDown Register
Eingebaute Widerstände einschalten
0x0C00: No PullUp, PullDown
01: PullUp
10: PullDown
11: Reserviert
IDRInput Data Register
Eingänge lesen
0x10
ODROutput Data Register
Ausgangswerte
0x14
BSRRBit Set/Reset Register
Einzelne Bits an- bzw. ausschalten
0x180..15 Setzen (hat Priorität)
16..31 Rücksetzen
LCKRConfiguration Lock Register
Einstellung der Pins sperren
0x1c
GPIO Register und ihre Funktion


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);
  }  
}