ESP32 OLED Battery Thing
Getting Started
Als ich voriges Jahr im Krankenhaus war, hat mein Arbeitskollege mir ein $ WEMOS TTGO ESP32 Board $ geschenkt. Coole Sache!
Es kommt mit den folgenden Features:
- ESP-WROOM-32 Module, inkl. WiFi und Bluetooth
- USB to Serial Bridge, Silicon Labs CP210X Chip
- Ladeschaltkreis für eine 18650 Zelle, inkl. Halterung auf der Rückseite
- OLED Display, SSD1306, I²C Version
- LED an GPIO 16
PinOut
Um euch das basteln zu erleichtern habe ich mal die Pinbelegung illustriert.
Arduino IDE
In der Arduino IDE müsst ihr in die Einstellungen gehen (Strg + ,) und bei den Zusätzlichen Boardmanager Links die URL von Espressif einfügen. Habt ihr hier schon andere Quellen eingetragen, können diese einfach mit Komma getrennt werden.
Der Link lautet: https://dl.espressif.com/dl/package_esp32_index.json
Danach geht es in den Boardverwalter, wo wir das Paket esp32 von Espressif installieren. Danach sollte bei Boards die Liste mit den neuen ESP32 Boards auftauchen. Auf dem linken Bild seht ihr bei mir, dass ich das WeMos Wifi&BluetoothBattery Board ausgewählt habe.
Damit können wir das Board programmieren. Jedoch gibt es eine Besonderheit: Man muss leider den Boot-Knopf gedrückt halten, wenn man Sketches hochladen will. Wenn ihr also eine Fehlermeldung bekommt, wie „Failed to connect to ESP32: Timed out waiting for packet header“ müsst ihr den Boot-Knopf drücken. Eine dauerhaftere Lösung ist verlinkt.
Sollte sich das OLED-Display nicht aktualisieren, bzw der neue Sketch nicht Starten, drückt den EN-Knopf. Achtet beim Hochladen natürlich auch darauf, den Kippschalter auf ON zu haben.
OLED Display
Zunächst installieren wir uns die Bibliothek für das SSD1306 Display. Hier gibt es ein spezielles Treiberpaket, extra für ESP32 Boards.
Beim OLED Display auf dieser Variante gibt es eine kleine Abweichung bei der Verbindung mit dem ESP32. Anders als in den Beispielen sind die I²C SDA und SCL Pins wie folgt angeschlossen:
- SCL – Pin 4 (SCL bedeutet Serial Clock – Taktleitung)
- SDA – Pin 5 (SDA bedeutet Serial Data – Datenleitung)
Das bedeutet in den Beispielen, oder auch in eurem Eigenen Programm müsst ihr in der Zeile wo SSD1306Wire display(0x3c, SDA, SCL); steht einfach SSD1306Wire display(0x3c, 5, 4); eintragen.
In Manchen Beispielen werden die Pins mit D4 oder D5 geschrieben, das funktioniert aber nicht. Spätestens beim Kompilieren wird die IDE meckern.
Ein Beispiel wäre wie folgt:
//ESP32 OLED Learning
//----Includes für die Bibliotheken für das kleine OLED-Display
#include <Wire.h>
#include "SSD1306Wire.h"
// Initialize the OLED display using Wire library
SSD1306Wire display(0x3c, 5, 4);
void printBuffer(void) {
// Initialisiert eine Funktion für Textausgabe
// allocate memory to store 5 lines of text and 30 chars per line.
display.setLogBuffer(5, 30);
// Hier kommt der Text rein
const char* test[] = {
"Moin",
"Wie geht es dir?",
"Wir spielen endlich mit dem",
"ESP32 Ding herum",
"Ist ziemlich cool!",
};
for (uint8_t i = 0; i < 5; i++) {
display.clear();
//Display flush
// Ausgabe (wie man es auch vom Seriellen Monitor kennt
display.println(test[i]);
// Draw it to the internal screen buffer
display.drawLogBuffer(0, 0);
// Display it on the screen
display.display();
delay(500);
}
}
void setup() {
display.init();
//display.flipScreenVertically();
//falls notwendig
display.setContrast(255);
}
void loop() {
printBuffer(); //Ruft die Funktion mit dem text ab.
delay(1000);
//warte 1 sekunde
display.clear();}
Jetzt sollte der Text auf dem kleinen Display erscheinen.
Solltet ihr noch andere Probleme haben gibt es <hier/> einen guten Troubleshooting Guide.
BLE
Als nächstes versuche ich die LED an GPIO 16 über BLE anzusteuern. Dazu brauchen wir eine Firmware und eine App. Ich bin jetzt kein guter Programmierer also greife ich für die App auf den MIT App-Inventor zurück.
Dazu müssen die entsprechenden Bibliotheken eingebunden werden und natürlich das Bluetooth-Gebimmel definiert werden.
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
BLEServer *pServer = NULL;
BLECharacteristic * pTxCharacteristic;
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
// UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
Bluetooth services brauchen immer eine Einzigartige ID (UUID = Universally Unique Identifier). Diese kann man hier einfach übernehmen oder lässt sie sich durch einen Zufallsgenerator wie <hier/> erstellen.
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
};
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
}
};
class MyCallbacks: public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
std::string rxValue = pCharacteristic->getValue();
if (rxValue.length() > 0) {
Serial.println("*********");
Serial.print("Received Value: ");
for (int i = 0; i < rxValue.length(); i++)
Serial.print(rxValue[i]);
Serial.println();
Serial.println("*********");
}
}
};
In die Void SetUp kommt folgender Code:
// Create the BLE Device
BLEDevice::init("UART Service");
// Create the BLE Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Create the BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create a BLE Characteristic
pTxCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID_TX,
BLECharacteristic::PROPERTY_NOTIFY
);
pTxCharacteristic->addDescriptor(new BLE2902());
BLECharacteristic * pRxCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID_RX,
BLECharacteristic::PROPERTY_WRITE
);
pRxCharacteristic->setCallbacks(new MyCallbacks());
// Start the service
pService->start();
// Start advertising
pServer->getAdvertising()->start();
Serial.println("Waiting a client connection to notify...");
In die Loop Funktion kommt der folgende Code:
if (deviceConnected) {
pTxCharacteristic->setValue(&txValue, 1);
pTxCharacteristic->notify();
txValue++;
delay(10); // bluetooth stack will go into congestion, if too many packets are sent
}
// disconnecting
if (!deviceConnected && oldDeviceConnected) {
delay(500); // give the bluetooth stack the chance to get things ready
pServer->startAdvertising(); // restart advertising
Serial.println("restart advertising");
oldDeviceConnected = deviceConnected;
}
// connecting
if (deviceConnected && !oldDeviceConnected) {
// do stuff here on connecting
oldDeviceConnected = deviceConnected;
}
Jetzt fehlt nur noch die Passende App auf dem Smartphone: TBA
Touch
Ein weiteres cooles Feature vom ESP32 ist die direkte integration von Touchfunktionen. An insgesamt 9 GPIO Pins lassen sich mit touchRead(); direkt im statischen Feld ausgeben. Wenn man mit einem Grenzwert (Threshold) Arbeitet, kann man so ziemlich leicht kleine Touch-Steuer-Elemente einbauen.
Mit einem Jumper-Kabel jeweils an Pin 32 und 33 kann man sich auf den Seriellen Monitor den wert Ausgeben lassen um die Funktion zu kalibrieren. (Bei mir etwa 130). Wenn ich das Kabel berühre sinkt der Wert auf etwa 20.
Für eine entprellte Toucheingabe des UI füge ich also folgenden Code hinzu::
//Pins Festlegen
#define touchPinLeft 32
#define touchPinRight 33
//Grenzwert Festlegen
#define thresholdTouchLeft 30
#define thresholdTouchRight 30
//Entprell-Variablen
byte touchLeftDurationMax = 40;
byte touchRightDurationMax = 40;
byte touchRightDuration = 0;
byte touchLeftDuration = 0;
//Zeitintervall zum Messen
const unsigned long touchIntervalMS = 50;
//Statusüberprüfung
byte prevTouchLState = LOW;
byte prevTouchRState = LOW;
byte currentTouchLState = LOW;
byte currentTouchRState = LOW;
//In welche Richtung soll die Anzeige "swipen"
bool swipeleft = false;
bool swiperight = false;
//Wie viele Displays will ich darstellen
int displayNum = 0; //Verschiedene Displays mit Taste zum Switchen
int maxDisplays = 5; //Wie viele max?
Dann brauche ich eine Reihe von funktionen, die für die verschiedenen Zustände etwas tun:
void touchLTouched() {
Serial.println("Left Touchfield is touched");
touchLeftDuration = 0; //Resets the Touch-Timer
}
void touchRTouched() {
Serial.println("Right Touchfield is touched");
touchRightDuration = 0; //Resets the Touch-Timer
}
//this is what we do when we release the touchfield
void touchLReleased() {
Serial.println("Left Touchfield is released");
//if it was pressed for longer than our max time
if (touchLeftDuration >= touchLeftDurationMax ) {
longTouchLeft();
}
else {
shortTouchLeft();
}
}
void touchRReleased() {
Serial.println("Right Touchfield is released");
//if it was pressed for longer than our max time
if (touchRightDuration >= touchRightDurationMax ) {
longTouchRight();
}
else {
shortTouchRight();
}
}
//this is what we do if the touchfield was touched long
void longTouchLeft() {
Serial.println("Left Touchfield was touched long");
}
void longTouchRight() {
Serial.println("Right Touchfield was touched long");
}
//this is what we do if the touchfield left was touched shortly
void shortTouchLeft() {
Serial.println("Left Touchfield was touched short");
swipeleft = true;
}
void shortTouchRight() {
Serial.println("Right Touchfield was touched short");
swiperight = true;
}
void checkLeftTouch() {
if ( touchRead(touchPinLeft) <= thresholdTouchLeft ) {
currentTouchLState = HIGH;
Serial.print("Left treshold in is: ");
Serial.println(touchRead(touchPinLeft));
}
else if ( touchRead(touchPinLeft) >= thresholdTouchLeft ) {
currentTouchLState = LOW;
}
//does it go from not touched to touched?
if ((prevTouchLState == LOW) && (currentTouchLState == HIGH)) {
touchLTouched();
}
//or does it go the other way?
if ((prevTouchLState == HIGH) && (currentTouchLState == LOW)) {
touchLReleased();
}
//if it is pressed, we count the time - we check in ms intervall
else if (currentTouchLState == HIGH && prevTouchLState == HIGH ) {
touchLeftDuration++;
Serial.println("TouchDuration is: " + String(touchLeftDuration));
}
// so next round the new one is the old
prevTouchLState = currentTouchLState;
}
void checkRightTouch() {
if ( touchRead(touchPinRight) <= thresholdTouchRight ) {
currentTouchRState = HIGH;
Serial.print("Right treshold in is: ");
Serial.println(touchRead(touchPinLeft));
}
else if ( touchRead(touchPinRight) >= thresholdTouchRight ) {
currentTouchRState = LOW;
}
//does it go from not touched to touched?
if ((prevTouchRState == LOW) && (currentTouchRState == HIGH)) {
touchRTouched();
}
//or does it go the other way?
if ((prevTouchRState == HIGH) && (currentTouchRState == LOW)) {
touchRReleased();
}
//if it is pressed, we count the time - we check in ms intervall
else if (currentTouchRState == HIGH && prevTouchRState == HIGH ) {
touchRightDuration++;
Serial.println("TouchDuration is: " + String(touchRightDuration));
}
// so next round the new one is the old
prevTouchRState = currentTouchRState;
}
int getDisplayNum() {
unsigned long touchTimePrev = 0;
//do this every x milliseconds (defined within touchIntervalMS)
if (millis() - touchTimePrev >= touchIntervalMS ) {
touchTimePrev = millis();
//Check if Left touchfield is touched or not
checkLeftTouch();
checkRightTouch();
}
if (swipeleft) {
if (displayNum == 0) {
displayNum = maxDisplays;
}
else {
displayNum--;
}
swipeleft = false;
}
if (swiperight) {
if (displayNum == maxDisplays) {
displayNum = 0;
}
else {
displayNum++;
}
swiperight = false;
}
return displayNum;
}
Außerdem eine Funktion die das eigentliche Display-Rendern übernimmt:
void printDisplay(int displayNum) {
switch (displayNum)
{
case 0:
display.clear();
timeOverlay();
statusOverlay();
break;
case 1:
display.clear();
timeOverlay();
statusOverlay();
display.setFont(ArialMT_Plain_24);
display.drawString(20, 31, String(millis()));
break;
case 2:
display.clear();
timeOverlay();
statusOverlay();
display.setFont(ArialMT_Plain_16);
display.drawString(0, 10, "Hello world");
break;
case 3:
display.clear();
timeOverlay();
statusOverlay();
display.setFont(ArialMT_Plain_10);
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.drawStringMaxWidth(0, 10, 128,
"Moin \n Dies ist ein Fließtext test. ABCDEFGHIJKLMNOPQRSTUVWXYZ" );
break;
case 4:
display.clear();
timeOverlay();
statusOverlay();
// The coordinates define the right end of the text
display.setTextAlignment(TEXT_ALIGN_RIGHT);
display.drawString(128, 33, "Right aligned (128,33)");
break;
case 5:
display.clear();
timeOverlay();
statusOverlay();
for (int i = 1; i < 8; i++) {
display.setColor(WHITE);
display.drawCircle(32, 32, i * 3);
if (i % 2 == 0) {
display.setColor(BLACK);
}
display.fillCircle(96, 32, 32 - i * 3);
}
break;
}
display.display();
delay(10);
}
Im Setup darf natürlich nicht die Display Initialisierung fehlen:
display.init();
display.clear();
display.flipScreenVertically();
display.setContrast(255);
Und im Loop rufen wir einfach folgendes auf:
printDisplay(getDisplayNum());
$ Die mit einem $ gekennzeichneten Links, sind Affiliate Links. Wenn du über diese in den Shop gelangst und etwas kaufst, bekomme ich eine kleine Provision
4 Kommentare
Super Anleitung . Du Hast meine Tag gerettet. Ich hab das Ding schon Ewigkeiten rumliegen , war aber immer gefrustet und kam nicht weiter . Hier zumindest ein Anfang.
Das hört man gerne:)
Hallo Daniel,
danke für Deine Publikation. Endlich funktioniert dieses Display!
Wie kann ich Serial.Print(„hallo Welt“); nutzen, ohne die Meldung „esp8266-oled-ssd1306/src
/OLEDDisplay.cpp“ zu erhalten?
Hello,
Also für den seriellen Monitor einfach zunächst ein Serial.begin(115200); in die void setup und dann Serial.print(„XYZ“); hier drauf achten, das print wird kleingeschrieben. Dann natürlich die passende baudrate im seriellen Monitor auswählen.
Falls das auf den OLED soll z.b. mit einer kleinen for-schleife:
for (uint8_t i = 0; i < 5; i++) { display.clear(); //Display flush // Ausgabe (wie man es auch vom Seriellen Monitor kennt display.println(test[i]); // Draw it to the internal screen buffer display.drawLogBuffer(0, 0); // Display it on the screen display.display(); delay(500); } } In test[] kannst du dann einfach deine Zeilen eintragen. Vll anstatt in der for-loop bis 5 zu zählen, kannst du die Länge von Test nehmen. Leider nur so aus dem Kopf beantwortet. Der Beitrag liegt ja eine Weile zurück und ich hab das Teil gerade nicht zur Hand zum Testen, da unterwegs. Ich schreibe vom Handy aus... Lg!