Ausgabe auf OLED-Display einheitlicher gestalten – Beispiel WLAN Scanner

Ich habe ein 1,3 Zoll monochromes OLED 128 x 64 Pixel Display mit der Bezeichnung SH1106 das per I2C angesprochen werden kann.
Schön klein (36 x 34mm) um auch bei kleinen Lösungen noch eine Anzeige mit einbauen zu können.
Da es sowieso nicht viel Platz für Informationsanzeige gibt, sind Strings mit gleichbleibender Textlänge für eine formatierte gut leserliche Anordnung von Vorteil. Somit sind Uhrzeit, Datum Temperatur usw. keine besondere Herausforderung.
Möchte ich aber auch unbekannte Textlängen auslesen und darstellen, müssen zu kurze oder zu lange Strings formatiert werden.

In dem folgendem WLAN Scanner Beispiel möchte ich zeigen, wie diese einheitlicher gestaltet werden können.

2,4 GHz WLAN Scanner mit ESP32-S2 Mini und einem 1.3 Zoll OLED Display
Als Beispiel Projekt soll ein einfacher ESP32, die Umgebung nach 2,4 GHz Accesspoints scannen und auf dem kleinen Display auflisten.
Benötigt habe ich lediglich einen ESP32 S2 mini, ein OLED Display und eine kleine Powerbank für die mobile Stromversorgung.

Hardware:

Lolin S2 MiniESP32 S2 Mini Datenblatt: S2 mini — WEMOS documentation
OLED Display SH11061_3_Zoll_Display_Datenblatt
Mini Powerbank Für mobile Stromversorgung

Erstellung Versuchsobjekt
Im ersten Schritt bekommt das ESP32 Entwicklungsboard von der Arduino IDE den Beispiel Sketch Wifi-Scan aufgespielt, um zu sehen, ob im Seriellen Monitor die gesuchten SSID´s aufgelistet werden.

Sollte der Code Upload bei einem Lolin S2 Mini nicht klappen, hier klicken

Versetze das S2-Board in den DFU-Modus (Device Firmware Upgrade) indem du beim Anschließen die Taste 0 gedrückt hältst.
Taste 0 erst wieder loslassen, wenn der Bestätigungston zu hören ist oder die Com-Schnittestelle im Gerätemanager erscheint.


Test Wifi Scan:

#include "WiFi.h"

void setup()
{
    Serial.begin(115200);

    // Set WiFi to station mode and disconnect from an AP if it was previously connected.
    WiFi.mode(WIFI_STA);
    WiFi.disconnect();
    delay(100);

    Serial.println("Setup done");
}

void loop()
{
    Serial.println("Scan start");

    // WiFi.scanNetworks will return the number of networks found.
    int n = WiFi.scanNetworks();
    Serial.println("Scan done");
    if (n == 0) {
        Serial.println("no networks found");
    } else {
        Serial.print(n);
        Serial.println(" networks found");
        Serial.println("Nr | SSID                             | RSSI | CH | Encryption");
        for (int i = 0; i < n; ++i) {
            // Print SSID and RSSI for each network found
            Serial.printf("%2d",i + 1);
            Serial.print(" | ");
            Serial.printf("%-32.32s", WiFi.SSID(i).c_str());
            Serial.print(" | ");
            Serial.printf("%4ld", WiFi.RSSI(i));
            Serial.print(" | ");
            Serial.printf("%2ld", WiFi.channel(i));
            Serial.print(" | ");
            switch (WiFi.encryptionType(i))
            {
            case WIFI_AUTH_OPEN:
                Serial.print("open");
                break;
            case WIFI_AUTH_WEP:
                Serial.print("WEP");
                break;
            case WIFI_AUTH_WPA_PSK:
                Serial.print("WPA");
                break;
            case WIFI_AUTH_WPA2_PSK:
                Serial.print("WPA2");
                break;
            case WIFI_AUTH_WPA_WPA2_PSK:
                Serial.print("WPA+WPA2");
                break;
            case WIFI_AUTH_WPA2_ENTERPRISE:
                Serial.print("WPA2-EAP");
                break;
            case WIFI_AUTH_WPA3_PSK:
                Serial.print("WPA3");
                break;
            case WIFI_AUTH_WPA2_WPA3_PSK:
                Serial.print("WPA2+WPA3");
                break;
            case WIFI_AUTH_WAPI_PSK:
                Serial.print("WAPI");
                break;
            default:
                Serial.print("unknown");
            }
            Serial.println();
            delay(10);
        }
    }
    Serial.println("");

    // Delete the scan result to free memory for code below.
    WiFi.scanDelete();

    // Wait a bit before scanning again.
    delay(5000);
}

Über den Seriellen Monitor in der Arduino IDE, konnte ich nun die einzelnen Accesspoint sehen, sowie auch die Signalstärke. Diese wird normalerweise als RSSI (Received Signal Strength Indicator) ausgedrückt und in dBm (Dezibel Milliwatt) gemessen. Man beachte die schon vorhandene Formatierung die mit Serial.printf erzeugt wurde.

Hier ist eine einfache Interpretation der RSSI-Werte
-30 dBm: Maximale Signalstärke, Du bist wahrscheinlich direkt neben dem Router.
-50 dBm: Signal ist sehr stark.
-60 dBm: Signal ist gut.
-70 dBm: Signal ist okay.
-80 dBm: Signal ist in der Regel nicht ausreichend für zuverlässige Datenraten.
-90 dBm: Das Signal ist so schwach, dass es eher unbrauchbar ist.


ESP32 und Display verbinden:
Nun wird das OLED Display angeschlossen. Verkabelung ist denkbar einfach.
SDA an Pin 33, SCK an Pin 35, GND an Ground und VCC an 3,3 Volt

ESP 32 S2 Mini: Belegungsplan ESP32 S2 Mini

Um gleich auf dem OLED Display etwas anzeigen zu lassen, muss im Sketch die I2C Adresse angegeben werden, Normalerweise ist bei einem SH1106 die I2C Adresse 0x3C. Ich suche aber immer gerne mit diesem Sketch danach, so kann ich zeitgleich testen ob ich die Hardware korrekt verdrahtet habe.

I2C Adresse suchen mit diesem Sketch:

#include "Wire.h"

void setup() {
  Serial.begin(115200);
  Wire.begin();
}

void loop() {
  byte error, address;
  int nDevices = 0;

  delay(5000);

  Serial.println("Scanning for I2C devices ...");
  for(address = 0x01; address < 0x7f; address++){
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    if (error == 0){
      Serial.printf("I2C device found at address 0x%02X\n", address);
      nDevices++;
    } else if(error != 2){
      Serial.printf("Error %d at address 0x%02X\n", error, address);
    }
  }
  if (nDevices == 0){
    Serial.println("No I2C devices found");
  }
}

Bei mir wurde im Seriellen Monitor das Display an der Adresse 0x3C gefunden. Schon mal gut 🙂

OLED Testen:
Schnell noch die SH110x Bibliothek von Adafruit Version 2.1.10 installiert


Sketch ohne String Formatierung:
Mit diesem Versuchsaufbau werde ich lediglich im Sketch die Werte wie im Seriellen Monitor auf dem Display anzeigen lassen. In der Umgebung sind derweil 6 SSID´s mit 7 Zeichen bzw. 9 Zeichen zu finden.

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>
#include "WiFi.h"

#define i2c_Address 0x3c
#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels
#define OLED_RESET -1     //   QT-PY / XIAO
Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();

  delay(250);                        // wait for the OLED to power up
  display.begin(i2c_Address, true);  // Address 0x3C default

  display.display();
  delay(2000);

  display.clearDisplay();
}

void loop() {
  int n = WiFi.scanNetworks();
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.setTextColor(SH110X_WHITE);
  display.println("Prilchen WLAN Scanner");
  display.println("Nr | SSID       |RSSI");

for (int i = 0; i < n; ++i) {
    display.print(i + 1);
    display.print(" | ");
    display.print(WiFi.SSID(i).c_str());
    display.print(" | ");
    display.println(WiFi.RSSI(i));
    delay(50);
  }

  display.display();

  delay(5000);
  display.clearDisplay();
}

Das was dann zu sehen ist, ist jedoch nicht tabellarisch angeordnet:

Sind in der Umgebung sogar SSID´s mit mehr als 11 Zeichen werden weniger Zeilen durch den entstehende Umbruch angezeigt:

Nun mit Formatierung:
In den ganz oben zu sehenden Beispiel Wifi Scan Sketch, kann man in der seriellen Monitorausgabe die Funktion Serial.printf(„%2d“,i + 1); sehen, das diese Zeile, die fortlaufende Ganzzahl mit 2 Zeichen darstellen soll.
Die Arduino-Bibliotheken bieten standardmäßig keine display.printf-Funktion an.
Allerdings kann man mit der sprintf-Funktion einen formatierten String erstellen und zusammen mit display.print verwenden.

Hier ist ein einfaches Beispiel:

char buffer[50]; // Array mit 50 Zeichen
int value = 123;
sprintf(buffer, „Wert: %d“, value);
display.print(buffer);

In diesem Code wird sprintf verwendet, um einen formatierten String zu erstellen und in buffer zu speichern. Dann wird display.print verwendet, um diesen String auf dem Display auszugeben.

Beispiel: sprintf(buffer, „%1d | %-11.11s | %3d“, i + 1, ssid.c_str(), WiFi.RSSI(i));

Hier eine Zusammenfassung der Format-Spezifikatoren, die in der sprintf-Funktion in C und C++ verwendet werden können:

%d oder %iWird für Ganzzahlen verwendet.
%fWird für Fließkommazahlen verwendet.
%cWird für Zeichen verwendet.
%sWird für Zeichenketten verwendet.
%%Gibt ein %-Zeichen aus.

Zahl nach % (z.B. %2d): Gibt die minimale Breite des Feldes an. Wenn der Wert kürzer als das Feld ist, werden die zusätzlichen Leerstellen auf der linken Seite des Werts eingefügt.

Minuszeichen vor der Zahl (z.B. %10d): Der Wert wird linksbündig im Feld ausgegeben. Die zusätzlichen Leerstellen werden auf der rechten Seite des Werts eingefügt.

Punkt zwischen Zahlen bei Zeichenketten (z.B. %11.11s): Die erste Zahl gibt die minimale Breite des Feldes an und die zweite Zahl nach dem Punkt, die maximale Breite des Feldes das ausgegeben werden sollen. Wenn die Zeichenkette länger als diese Zahl ist, wird sie gekürzt.

Bei Fließkommazahlen (z.B. %10.2f) gibt die zweite Zahl nach dem Punkt, die Anzahl der Nachkommastellen an, die bei Fließkommazahlen ausgegeben werden sollen.

Bitte immer beachten dass der Puffer groß genug ist, um alle Daten aufzunehmen, die hineinschreiben werden könnten, um einen Pufferüberlauf zu vermeiden, der zu undefiniertem Verhalten führen kann.

Finaler Sketch:

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>
#include "WiFi.h"

#define i2c_Address 0x3c
#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels
#define OLED_RESET -1     //   QT-PY / XIAO
Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();

  delay(250);                        // wait for the OLED to power up
  display.begin(i2c_Address, true);  // Address 0x3C default

  display.display();
  delay(2000);

  display.clearDisplay();
}

void loop() {
  int n = WiFi.scanNetworks();
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.setTextColor(SH110X_WHITE);
  display.println("Prilchen WLAN Scanner");
  display.println("Nr| SSID        |RSSI");
char buffer[128]; // Buffer für formatierten String

for (int i = 0; i < n; ++i) {
  sprintf(buffer, "%1d | %-11.11s | %3d", i + 1, WiFi.SSID(i).c_str(), WiFi.RSSI(i));
  display.println(buffer);
  delay(50);
}

  display.display();

  delay(5000);
  display.clearDisplay();
}

Wer mehr zu den grafischen Möglichkeiten aus der Adafruit Bibliothek ausprobieren möchte, sollte diese Dokumentation aufsuchen:
https://learn.adafruit.com/adafruit-gfx-graphics-library

,