Wichtige Info

Die Inhalte, die du hier siehst stelle ich dir ohne Werbeanzeigen und ohne Tracking deiner Daten zur Verfügung. Trotzdem muss ich die Server bezahlen sowie Zeit in Recherche, Umsetzung sowie Mail Support stecken.
Um dies leisten zu können, verlinke ich in einigen Artikeln auf die Plattform Amazon. Alle diese Links nennen sich Afiliate Links. Wenn du dir mit diesem Link etwas kaufst, dann erhalte ich eine kleine Provision. Dies ändert jedoch NICHT den Preis, den du bezahlst!
Falls du mich also unterstützen möchtest, kannst du auf den Link zum Produkt klicken und hilfst mir dabei, dieses Hobby weiter zu betreiben.
Da ich Keine Werbung schalte und keine Spenden sammle, ist dies die einzige Möglichkeit, meine Systeme und mich zu finanzieren. Ich hoffe du kannst das verstehen :)



ESP IDF - Pinmode, GPIO und Ausgaänge/Eingänge


Einleitung

Dieer Beitrag ist Bestandteil meiner Serie zum SDK ESP-IDF.

Das Steuern von Ein- und Ausgängen ist wohl mit das wichtigste bei jedem Projekt, welches mit Mikrocontrollern zu tun hat. Sei es die LED, welche An oder Aus gehen soll, das Auslesen eines Temperatursensors oder das Steuern eines Displays. Alles benötigt die externe Verbindung zu anderen Geräten.
Arduino hat viele Entwickler beid er Konfiguration mit der Verwendung der Funktion pinMode abgeholt. Sowas ähnliches, jedoch in deutlich mächtiger, gibt es auch in ESP-IDF (obvious, schließlich basiert Arduino für ESP, auf dem SDK ESP-IDF :P ).

In diesem Beitrag wollen wir uns anschauen, wie die Ein- und Ausgänge in ESP-IDF konfiguriert werden und welche Möglichkeiten man hat.

Hauptteil

Zuersteinmal sollten wir definieren, was so ein Pin denn alles kann. Es ist beim ESP durchaus nicht mit Ein- oder Ausgang getan. Ich rede hier jedoch nicht von der Art des Pin (z.B. DAC, ADC oder PWM) sondern welche Funktion ein Pin per Software in ESP-IDF abbilden kann und dafür schauen wir uns die folgende Funktion an:

esp_err_t gpio_config(const gpio_config_t *pGPIOConfig)

Diese Funktion dient zum Konfigurieren der Pin bzw. mehrerer GPIO Pins auf dem board. Man sieht, dass man eine Konfiguration vom Typ gpio_config_t angeben muss, welche wie folgt aussieht:

typedef struct {
    uint64t pinbit_mask;          /!< GPIO pin: set with bit mask, each bit maps to a GPIO /
    gpiomodet mode;               /!< GPIO mode: set input/output mode                     /
    gpiopullupt pullupen;       /!< GPIO pull-up                                         /
    gpiopulldownt pulldownen;   /!< GPIO pull-down                                       /
    gpiointtypet intrtype;      /!< GPIO interrupt type                                  /
#if SOCGPIOSUPPORTPINHYS_FILTER
    gpiohysctrlmodet hysctrlmode;       /!< GPIO hysteresis: hysteresis filter on slope input    /
#endif

} gpioconfigt;

Dies ist das struct, welches unsere Pins konfiguriert und hier wollen wir nun mal das wichtigste abarbeiten

pinbit_mask

Die Komponente "pinbit_mask" definiert wohl mit das wichtigste in dieser Config, da diese angibt, welche Pins betroffen sind. Je nachdem wie tief du mit der Sprache C bist, wird dir der Name bereits verraten, was zu tun ist.
Um den/die Pins zu definieren, wird hier eine sog. Bit Maske gefordert, also in dem Fall ein 32 Bit langer Wert, welcher alle benötigten Pins mithilfe von Bis Shifts und logischer OR verknüpfung entzhält.

Ein Beispiel:

Wir möchten den Pin GPIO_NUM_20 definieren. In diesem Fall, müssen wir uns zuerst eine Variable anlegen, welche 32 BIT lang ist und dann mit dem zugeordneten PIN Wert (bei ESP ist es die GPIO Nummer z.B. hier 20) einen links Shift durchführen. Heißt hier das folgende:

uint64t pin_bit_mask = (1ULL << GPIO_NUM_20);

Mit dieser Zeile haben wir eine neue 32 Bit lange Variable erstellt, welche den Wert 1 besitzt (1 Unsingned Long Long). Auf diese Weise haben wir mit Sicherheit einen 32 Bit langen Wert und keinen Int, was ggf. zu Problemen führt. Sobald diese angelegt ist, führen wir durch den Operator "<<" einen sog. Linksshift durch. Wie weit dieser geht, wird durch den rechten Wert (Hier GPIO_NUM_20, was auf 20 aufgelöst wird) definiert. +

Was ist aber, wenn wir nun mehrere Pins haben? - Auch dafür gibt es eine Lösung, indem wir die Operationen verketten mit einem logischen "Oder".

uint64t pin_bit_mask = ((1ULL<<GPIO_NUM_20) | (1ULL<<GPIO_NUM_21) | (1ULL<<GPIO_NUM_22))

Hier kannst du nun sehen, dass 3 Pins definiert werden (GPIO 20 - 22). Diese werden mithilfe eines logischen oder verknüpft, das heißt, dass an jeder Stelle, der Pin Shifts (20, 21 und 22) eine 1 gesetzt werden wird, da 0 | 1 = 1 ist.

mode

Als nächstes haben wir den pin Mode, dieser hält einiges auf Lager, aus diesem Grund gehe ich nur kurz über die jeweiligen Werte. Mehr kannst du hier lesen.

  • GPIOMODEINPUT: Der Pin ist im Eingangsmodus, er wird zum Lesen von Daten verwendet.
  • GPIOMODEOUTPUT: Der Pin ist im Ausgangsmodus, er wird zum Senden von Daten verwendet.
  • GPIOMODEINPUTOUTPUT: Der Pin kann sowohl für Eingang als auch für Ausgang im Modus mit offenem Kollektor konfiguriert werden.
  • GPIOMODEOUTPUT_OD: Der Pin ist im Ausgangsmodus mit offenem Kollektor, was nützlich sein kann, wenn du eine externe Last steuern möchtest.
  • GPIOMODEINPUTOUTPUTOD: Der Pin kann sowohl für Eingang als auch für Ausgang im Modus mit offenem Kollektor konfiguriert werden.
  • GPIOMODEDISABLE: Der Pin ist deaktiviert.
intrtype

Dieser Wert ist ziemlich interessant, da wir hier definieren können, wann der Pin einen Interrupt auslösen soll, zum Beispiel wenn ein Knopf gedrückt wird. Hier gibt es ebenfalls einige Modis, welche ich nur kurz überfliegen werde.

  • GPIOINTRDISABLE: Interrupt deaktivieren. - zum Beispiel für Outputs nutzbar.
  • GPIOINTRANYEDGE: Interrupt bei steigender oder fallender Flanke auslösen. - Nützlich, wenn ein und aus detektiert werden sollen.
  • GPIOINTRNEGEDGE: Interrupt bei fallender Flanke auslösen.
  • GPIOINTRPOSEDGE: Interrupt bei steigender Flanke auslösen. - Hier würde bei dem Beispiel Knopf nur die steigende Flanke (also Knopf drücken) detektiert werden (kommt natürlich auf die Konfiguration an. Wenn der Knopf auf Ground schaltet und damit das Signal dann fällt, würde es sich anders herum verhalten ^^).
  • GPIOINTRHIGH_LEVEL: Interrupt bei hohem Pegel auslösen. - Hier das selbe Spiel, aber der Interrput kommt erst, bei hohem Pegel und nicht schon beim wechsel. Es wird sozusagen auf einen stabilen hohen Pegel gewartet. Hier hat man z.B. nicht das Risiko von Bounce effekten, welcher bei vorherigem Modus beachtet werden muss.
  • GPIOINTRLOW_LEVEL: Interrupt bei niedrigem Pegel auslösen.
pullupen / pulldownen

Die Werte für diese beiden Komponenten sind je 0x0 oder 0x1 also True/False. Je nachdem, was man hier definiert, wird der interne Pull-Up oder Pull-Down Wiederstand aktiviert oder halt deaktiviert.

hysctrlmode

Zuletzt haben wir hier noch den sog. Hysterese Mode. Dieser dient dazu um Prelleffekte (Bouncing) bei z.B. Schaltern oder anderen (dynamischen) Sensoren / Signalquellen zu minimieren oder im besten Fall zu unterdrücken um einen eindeutigen Zustand zu wahren (also 1 oder 0). Dieser Modus wird jedoch nicht von allen Systemen unterstützt. Zusammengefasst, kann dieser Modus bei Signalen nahe der Schaltschwelle entscheiden, ob bereits in einen anderen Zustand gewechselt werden soll oder nicht. Dadurch können im besten Fall Fehlauslösungen komplett unterdrückt werden. Hier ist der mögliche Wert ebenfalls 0x0 oder 0x1, also True doer False.

Ein Beispiel

Zuletzt gibt es hier jetzt noch 2 Beispiele, wie eine Konfiguration aussehen kann, für einen Input bzw. Output.

#include <stdio.h>
#include "driver/gpio.h"

//define all needed led pins
#define OkLED GPIO_NUM_2
#define WarnLED GPIO_NUM_3
#define ErrorLED GPIO_NUM_12
#define InfoLED GPIO_NUM_13

//Define all needed button pins
#define Btn1 GPIO_NUM_20
#define Btn2 GPIO_NUM_21
#define Btn3 GPIO_NUM_22

//Create the bit mask for each output and input pins.
#define GPIO_BIT_MASK_OUTPUT  ((1ULL<<OkLED) | (1ULL<<WarnLED) | (1ULL<<ErrorLED) | (1ULL<<InfoLED)) 
#define GPIO_BIT_MASK_INPUT  ((1ULL<<Btn1) | (1ULL<<Btn2) | (1ULL<<Btn3)) 

void app_main(void)
{
    
    gpio_config_t o_conf;
	o_conf.intr_type = GPIO_INTR_DISABLE;
	o_conf.mode = GPIO_MODE_OUTPUT;
	o_conf.pin_bit_mask = GPIO_BIT_MASK_OUTPUT;
	o_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
	o_conf.pull_up_en = GPIO_PULLUP_DISABLE;
    esp_err_t res_output_pin_conf = gpio_config(&o_conf);

    gpio_config_t i_conf;
	i_conf.intr_type = GPIO_INTR_ANYEDGE;
	i_conf.mode = GPIO_MODE_INPUT;
	i_conf.pin_bit_mask = GPIO_BIT_MASK_INPUT;
	i_conf.pull_down_en = GPIO_PULLDOWN_ENABLE;
	i_conf.pull_up_en = GPIO_PULLUP_DISABLE;
    esp_err_t res_input_pin_conf = gpio_config(&i_conf);

    while(1)
    {
        if (gpio_get_level(Btn1) == 1) {
            
            gpio_set_level(OkLED, 1);
            gpio_set_level(InfoLED, 1);
        } else {
            gpio_set_level(OkLED, 0);
            gpio_set_level(InfoLED, 0);
        }
    }
}

Dies ist ein sehr einfaches Beispiel. Mit den Variablen res_input_pin_conf und res_output_pin_conf könnte vor dem eigentlichen Programmstart noch geprüft werden, ob alles funktioniert hat. Hier kann man prüfen, ob die Variable entweder "ESP_OK" (hier 0) oder alternativ "ESP_FAIL" (hier -1) entspricht.

Das wars auch schon, mit diesem Beispiel würde der Knopf bei Druck die OkLED und InfoLED aktivieren.


Back…