Analog-Digital Wander ADS1115 als Voltmeter am ESP32 (ESP32 OLED)

In meinem letzten Projekt hatte ich den Analog Digital-Wandler ADS1115 an ein ESP32 Lolin32 Board über den I2C-Bus angeschlossen, um damit Spannungen bis 5.3 Volt zu messen.

Diese hatte ich über die serielle Schnittstelle auf dem Serial Monitor angezeigt. Dabei habe ich auch herausgefunden, dass der ADS1115-ADC (Analog Digital Converter) "nur" 860 Samples pro Sekunde liefern kann - zuwenig für ein Oszilloskop. Damit musste ich die ursprüngliche Idee eines Oszilloskopes auf dem Odroid-Go (auch mit ESP32) TFT mit 320x240 Pixeln verwerfen.

Als Anzeige-Plattform wäre ein 128 x 64 Pixel OLED völlig ausreichend. Außer der Spannung könnte ich noch eine kleine Balkengrafik anzeigen, die den Verlauf der letzten 128 Messungen über die gesamte Breite des OLED darstellt.

Doch zuerst waren ein paar Hürden bezüglich des Boards zu überwinden.

Das ESP32 OLED Board

Das ESP32 OLED Board war ein Schnellkauf auf eBay von mir, weil mir die Idee eines integriertes Displays gleich gefiel und sinnvoll erschien. Eigentlich hat man immer etwas auszugeben oder anzuzeigen. Und das 128x64 Pixel OLED hatte ich in einem STM32 Blue Pill angeschlossen und dort kennengelernt.

Alles was der eBay Verkäufer zu dem Board an Informationen lieferte war: ESP-WROOM-32 ESP32 ESP-32 0.96" OLED Display WIFI-BT for Arduino AP STA Description: ESP32 has integrated antenna and RF Barron, power amplifier, low noise amplifier, filter and supply Management module. The entire solution is the smallest PCB area. The board is 2.4 GHz dual-mode Wi-Fi and Bluetooth low power technology, with a wide range of safe, reliable and scalable applications for the TSMC 40nm chips, power and best RF features. Features: high-performance price ratio, small size, easy embedding to other products powerful, with the support of the LwIP protocol, support three modes: using AP, STA and AP and STA LUA program support, easy to develop Kein Pinout, keine sonstige Hilfe zu den Anschlüssen. Also schaute ich bei anderen Händlern, aber auch die hatte nur diesen (oder einen sehr ähnlichen) nichts weiter aussagenden Text zu offerieren.

Ich bekam so langsam Zweifel, wo ich denn mein I2C-Anschluss für meinen ADS1115 hätte, denn der wäre evtl. nicht herausgeführt und nur am Display angelötet. Das Board selbst war auch nicht toll beschriftet. Manchmal stand an einem Pin nur die GPIO-Portnummer dran, manchmal eine seltsame Bezeichnung wie "SVP", "SVN", "SI", "CM", "SO" oder "CL", die mir alle nichts sagten.

Schließlich stolperte ich über die Seite, die eine SSD1306-Library, also einer Bibliothek für diesen OLED-Typ, von ThingPulse empfahl und nebenbei erwähnte, dass bei der Initialisierung die verwendeten I2C-Ports anzugeben waren, nämlich 5 für SDA und 4 für SCL und dass das Board mit dem Lolin32-Eintrag unter Werkzeuge in der Arduino IDE zu flashen sei.

Die I2C-Ports waren damit klar und ich hatte wenigstens einen Teil der Pins herausgefunden. Der Rest muss sich während der Arbeit mit dem Board herausstellen.

Den SPI-Bus habe ich mal für mich auf die Pins 12, 13, 14, 15 gelegt. Die liegen schön nebeneinander und scheinen keine andere Zweitbelegung zu haben. Mit Soft-SPI sollten sich hier ein SPI-Bus realisieren lassen.

Dem Rest der Pins habe ich erstmal keine weitere Bedeutung zugemessen, außer vielleicht der Leitungen, an denen man die Touch-Sensor-Funktionalität des ESP32 benutzen kann: T0 bis T6.

Herausgekommen ist das rechts abgebildete Pinout.

Eine andere Eigenart des Boards ist, das man im Gegensatz zum Lolin32-Board keine augelötete LED gibt, was nicht nicht weiter schlimm ist, weil man ja alles über das OLED anzeigen kann. Trotzdem: es gibt nicht einmal eine Power-LED, die anzeigen würde, dass das Board Saft hat. Auf der anderen Seite: das vergeudet dann auch keinen Strom.

Was mehr nervt ist das Design des Boards. Erstmal passt es nur so auf ein großes Breadboard, dass nur eine Pinreihe zugänglich ist. Man muss - wenn nötig - eine Reihe "unterirdisch" herausführen. Und dann gibt es noch zwei Buttons auf der Unterseite der Platine, wovon man den einen (den äußeren, bei mir falsch beschriftet) jedesmal drücken muss, wenn man eine neue Firmware flashen will - was durchaus öfter vorkommt.

Erst hatte ich mich gewundert, warum Arduino die Firmware nicht flashen will und immer mit einem Timeout abbricht. Bis ich mich dann an eine Eigenart der Blue Pill mit STM32dunio Bootloader erinnerte: dort muss ich beim Flashen kurz den Reset-Button drücken, wenn die LED auf- und abschwellt.

So ähnlich ist es auch bei diesem Board. Immer wenn die Meldung esptool.py v2.6 Serial port COM31 Connecting........_____....._____....._____....._____....._ unten in der IDE erscheint, muss man den äußeren Knopf an der Unterseite des Boards für etwa 1 Sekunde lang drücken. Und selbst das klappt nicht immer, evtl. muss man mehrmals drücken. Aber schließlich lässt das Board die Programmierung zu und lässt sich flashen. Ich finde das persönlich eine fummelige Sache. Es gibt aber wohl auch Boards, die den EN-Button auf der Oberseite haben.

OLED-Library von ThingPulse


Als ich die ThingPulse-Library für das OLED aus Github heruntergeladen und installiert hatte, wurde mir dann gleich klar, dass ich auf dem richtigen Weg bin. Einer der Demos sah exakt so aus, wie das, mit dem das ESP32 OLED Board ausgeliefert wurde. Ich habe zur Installation den folgenden Weg benutzt: und schon waren die Beispiele abrufbar. Unter Sketch / Bibliothek einbinden gibt es in der IDE wohl auch einen Eintrag, um .zip-Libraries einzubinden. Ist vielleicht einfacher oder sicherer. Bei mir hat es wie oben beschrieben funktioniert.

Eventuell ist die Library auch über den Bibliotheksverwalter abrufbar, dann dort nach dem Eintrag "ESP8266 and ESP32 OLED driver for SSD1306 displays by ThingPulse, Fabrice Weinberg" suchen.

Auf der Github-Seite zur Library findet sich dann auch eine Anleitung, wie das OLED anzusprechen ist und welche Funktionen die Library bietet.

Auf die wichtigsten will ich kurz eingehen:

Einbinden und Start der Library

I2C with Wire.h #define SDA 5 #define SCL 4 #include #include "SSD1306Wire.h" // for 128x64 displays: SSD1306Wire display(0x3c, SDA, SCL); // ADDRESS, SDA, SCL // for 128x32 displays: // SSD1306Wire display(0x3c, SDA, SCL, GEOMETRY_128_32); // ADDRESS, SDA, SCL void setup(void) { display.init(); } ... Ein sehr schöner Nebeneffekt des Einbindens der OLED-Library ist, dass diese den Wire mit der Info der richtigen Pins für SDA und SCL versorgt und andere I2C Komponenten dann auch an diesen Pins funktionieren. Da man das Display eigentlich immer braucht (wozu hätte man sonst ESP32 Board mit OLED gekauft), sollte das Einbinden und Initialisieren des OLED der erste Schritt im Programm sein.

API

Mit den API-Funktionen der Library kann man z. B. eigene Fonts einbinden (wobei die großen viel besser aussehen als bei der adafruit-Library), das Display um 180° drehen, oder Spiegeln, es ein- und ausschalten, es löschen, es invertieren und auch Heligkeit und Kontrast einstellen.

Man kann einzelne Pixel setzen. Linien, Rechtecke und Kreise, auch ausgefüllt zeichnen, Bitmaps anzeigen und sogar an einen Fortschrittsbalken wurde gedacht.

Natürlich kann man auch Text ausgeben. Dabei kann man die Schriftart setzen, die Ausrichtung setzen (linksbündig, mittig, rechtsbündig) und natürlich Text ausgeben.

Es gibt sogar einen Modus zum Abspielen von Animationen, auf die ich hier aber nicht näher eingehe. Das kann aber auf der Github-Seite nachgelesen werden.

Display Control

// Initialize the display void init(); // Free the memory used by the display void end(); // Cycle through the initialization void resetDisplay(void); // Connect again to the display through I2C void reconnect(void); // Turn the display on void displayOn(void); // Turn the display offs void displayOff(void); // Clear the local pixel buffer void clear(void); // Write the buffer to the display memory void display(void); // Inverted display mode void invertDisplay(void); // Normal display mode void normalDisplay(void); // Set display contrast // really low brightness & contrast: contrast = 10, precharge = 5, comdetect = 0 // normal brightness & contrast: contrast = 100 void setContrast(uint8_t contrast, uint8_t precharge = 241, uint8_t comdetect = 64); // Convenience method to access void setBrightness(uint8_t); // Turn the display upside down void flipScreenVertically(); // Draw the screen mirrored void mirrorScreen();

Pixel drawing

/* Drawing functions */ // Sets the color of all pixel operations void setColor(OLEDDISPLAY_COLOR color); // Draw a pixel at given position void setPixel(int16_t x, int16_t y); // Draw a line from position 0 to position 1 void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1); // Draw the border of a rectangle at the given location void drawRect(int16_t x, int16_t y, int16_t width, int16_t height); // Fill the rectangle void fillRect(int16_t x, int16_t y, int16_t width, int16_t height); // Draw the border of a circle void drawCircle(int16_t x, int16_t y, int16_t radius); // Fill circle void fillCircle(int16_t x, int16_t y, int16_t radius); // Draw a line horizontally void drawHorizontalLine(int16_t x, int16_t y, int16_t length); // Draw a lin vertically void drawVerticalLine(int16_t x, int16_t y, int16_t length); // Draws a rounded progress bar with the outer dimensions given by width and height. Progress is // a unsigned byte value between 0 and 100 void drawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t progress); // Draw a bitmap in the internal image format void drawFastImage(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *image); // Draw a XBM void drawXbm(int16_t x, int16_t y, int16_t width, int16_t height, const char* xbm);

Text operations

void drawString(int16_t x, int16_t y, String text); // Draws a String with a maximum width at the given location. // If the given String is wider than the specified width // The text will be wrapped to the next line at a space or dash void drawStringMaxWidth(int16_t x, int16_t y, int16_t maxLineWidth, String text); // Returns the width of the const char* with the current // font settings uint16_t getStringWidth(const char* text, uint16_t length); // Convencience method for the const char version uint16_t getStringWidth(String text); // Specifies relative to which anchor point // the text is rendered. Available constants: // TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER_BOTH void setTextAlignment(OLEDDISPLAY_TEXT_ALIGNMENT textAlignment); // Sets the current font. Available default fonts // ArialMT_Plain_10, ArialMT_Plain_16, ArialMT_Plain_24 // Or create one with the font tool at http://oleddisplay.squix.ch void setFont(const uint8_t* fontData);

Anschluss und Schaltung




Das Lolin32-Board aus der letzten Schaltung wird mit einem ESP32 OLED Board ausgetauscht.

Die I2C-Jumperkabel werden an Pin 5 (SDA) und 4 (SCL) des ESP32 angeschlossen. Außerdem habe ich noch einen 10 kΩ Widerstand zwischen A0 des ADS1115 und Masse gesteckt, damit die Flatterspannung weggeht, wenn nichts gemessen wird. Ansonsten bleibt an der Schaltung alles beim Alten.

Auf dem OLED wird die Spannung oben als Text angezeigt und unten wird im Bereich unter der Schrift eine kleine Balkengrafik angezeigt, die alle x Millisekunden aktualisiert wird und einen neuen, kleinen, 1 Pixel breiten Balken zeichnet. Ist das Display mit Balken vollgezeichnet, wird die Grafik gelöscht und wieder von vorne begonnen. Die Höhe der Balken ist abhängig von der Spannung. Je höher die Spannung, desto höher der Balken. Dabei kann eine Maximalspannung (mvMax) angegeben werden - eine Messung mit der Maximalspannung füllt den Balken dann voll.

Ich habe auch noch eine zweite Schaltung aufgebaut, um Ausschläge im Messverlauf besser demonstrieren zu können. Dazu kommt ein alter Bekannter, das Mikrofon-Modul KY-037 zum Einsatz:

Immer, wenn ich bei dieser Schaltung hineinpuste (oder brülle), sollte sich ein Ausschlag zeigen. Damit das besser sichtbar wird, habe ich mvMax auf 1500 gesetzt, da dies der Maximalwert ist, den das Mikrofon liefert. Gut beobachten lässt sich das im Video am Ende dieses Artikels.

Source-Code

//////////////////////////////////////////////////////// // (C) 2020 by Oliver Kuhlemann // // Bei Verwendung freue ich mich über Namensnennung, // // Quellenangabe und Verlinkung // // Quelle: http://cool-web.de/esp8266-esp32/ // //////////////////////////////////////////////////////// #define PinSCL 4 // I2C-Bus für OLED #define PinSDA 5 #include <Wire.h> #include <Adafruit_ADS1015.h> // für AD-Wandler #include "SSD1306Wire.h" // für OLED (legacy: #include "SSD1306.h") Adafruit_ADS1115 ads; // Objekt ads instanziieren // Initialize the OLED display using Arduino Wire: SSD1306Wire oled(0x3c, PinSDA, PinSCL); // ADDRESS, SDA, SCL void setup(void) { Serial.begin(115200); oled.init(); //oled.flipScreenVertically(); // falls anders herum eingebaut oled.clear(); oled.setTextAlignment(TEXT_ALIGN_CENTER); oled.setFont(ArialMT_Plain_24); oled.drawString(64, 15, "Voltmeter"); oled.display(); ads.setGain(GAIN_TWOTHIRDS); // 2/3x gain +/- 6.144V 1 bit = 3mV 0.1875mV (default) ads.begin(); ads.startComparator_SingleEnded(0, 1000); // Setup 3V comparator on channel 0 delay(2000); } void loop(void) { int16_t adc0; int16_t mv=0; char buf[100]; int h=0; int mvMax=6000; oled.setTextAlignment(TEXT_ALIGN_CENTER); oled.setFont(ArialMT_Plain_24); while (1) { oled.clear(); for (int x=0; x<128; x++) { // über die gesamte Breite die Spannung als Balken darstellen adc0 = ads.getLastConversionResults(); // Comparator will only de-assert after a read mv = adc0 * 0.1875; // Faktor bei 2/3x gain Serial.print(mv); Serial.println(" mV"); // nur den letzten Anzeigewert löschen, Balken lassen oled.setColor(BLACK); oled.fillRect(0, 0, 127, 23); oled.setColor(WHITE); snprintf (buf, 99, "%4d mV", mv); oled.drawString(64, 0, buf); // höhe des Ausschlags berechnen. zur Verfügung stehen Pixel 24-63 = 40 px // maximum 6V = 6000 mV // damit ist jeder Pixel 6000/40 = 150 mV hoch mvMax=6000; h=mv/(mvMax/40); // oled.setPixel (x, 63-h); oled.drawVerticalLine(x, 63-h, h+1); delay(100); oled.display(); } } } Der Quellcode ist wie immer gut mit Kommentaren versorgt, so dass klar sein sollte, wie alles funktioniert.

Video

Zum Abschluss noch ein kleines Video, indem ich die Mikrofon-Schaltung vorführe und noch einmal eine Batterie und eine USB-Powerbank durchmesse. Ein kleiner Scherz über die fehlerhafte Beschriftung der ESP32 OLED Platine sei mir auch erlaubt.