Beschleunigungskurve mit Odroid-Go, MMA8451 und TFT_eSPI programmieren

Der Odroid-Go ist nicht nur eine prima Retro-Spielekonsole, sondern auch eine sehr gut ausgestattete Plattform zum Programmieren mit dem ESP 32. Denn zusätzlich zu diesem sind auch schon Tasten, ein Display, ein Lautsprecher, ein microSD-Karten Slot, ein Expansion-Port und ein Akku (!) eingebaut. Ideal für mobile Experimente. Wer darüber genaueres wissen will, sieh sich bitte meinen Artikel bei Golem an.

Bei HARDKERNEL gibt es eine recht ausführliche Anleitung mit vielen Beispielen, die zeigt, wie man den Odroid-Go mit der von HARDKERNEL dafür vorgesehenen Bibliothek programmieren kann.

Mobile Experimentierplattform

Für die Entwicklung eines durch einen Beschleunigungssensor getriggerten Bremslichts wollte ich den Kurvenverlauf der drei 3 Achsen eines Beschleunigungssensors direkt auf das Display des Odroid-Go zeichnen, so dass ich diesen am Fahrradlenker befestigen und beim Fahren beobachten konnte. Wer sich für die Funktionsweise eines Beschleunigungssensors interessiert, sei auf meinen ebenfalls bei Golem veröffentlichten Artikel verwiesen.

Falls Ihr das Nachbauen wollt, hier ein paar Hinweise: Als Beschleunigungssensor verwende ich den MMA8451 von Adafruit. Es gibt ihn zum Beispiel hier. Dieser muss folgendermaßen mit dem Odroid-Go über dessen Expansion Port verbunden werden:

  • SDA an 4
  • SCL an 5
  • GND an 1
  • VIN an 6

Wenn der Verlauf des Graphen vom Start weg schick aussehen soll, so sollte er auf der linken Seite beginnen und beim Erreichen des rechten Bildschirmverlaufs von rechts nach links scrollen. Dies zu realisieren, stellte sich für mich als nicht so einfach heraus.

Wie löst man so eine Aufgabe?

Für den Graphen benötigt man die kontinuierlich erfassten Messwerte der 3 Achsen des Beschleunigungssensors. Diese werden passend auf die Höhe des Displays (240 Pixel) skaliert und nebeneinander auf der X-Achse aufgetragen. Dabei wird mit ‘drawLine’ jeweils für zwei nebeneinander liegende Punkte eine Linie gezogen. Würde man einfach nur Punkte zeichnen, wäre das nicht besonders schön anzusehen. Wenn ich auf diese Weise am rechten Rand angekommen bin, muss ich den bisher gezeichneten Graph um einen Pixel nach links verschieben und kann dann den neuesten Messwert am rechten Rand einzeichnen. Auf diese Weise wandert am Anfang ein Linienverlauf von links nach rechts und wird anschließen kontinuierlich nach links verschoben.

Um das zu realisieren könnte ich nach dem Zeichnen des letzten Punktes auf der rechten Seite, den Bildschirm löschen und alle Punkte links von diesem, um einen Pixel versetzt, noch einmal zeichnen. Da würde der Bildschirm aber ganz schön flimmern, sofern es kein Double-Buffering gibt. Nach meinen Recherchen gibt es das aber bei der Standard-Bibliothek zum Odroid-Go nicht.

Einfach anfangen

Wie auch immer, ich hatte eine Idee und einen Lösungsansatz und habe angefangen zu programmieren. Um den Graphen mehrfach zeichnen zu können, müssen die abgefragten Werte irgendwo aufbewahrt werden. Ich habe dazu für jede Achse einen Array, sowie einen Zeiger auf die aktuelle Position darauf, verwendet. Erreicht der Zeiger die letzte Array-Position, so wird er wieder auf 0 gesetzt. Also ein Ringpuffer.

Das Ergebnis hat zwar funktioniert, mich aber nicht zufrieden gestellt. Das Merkwürdigste war eine nach wenigen Sekundenbruchteilen auffallende Ausfranzung des Graphen, die sich nach sehr langer Fehlersuche als, von einer eingelegten microSD-Karte abhängiges, Artefakt herausstellte. Aber auch der Rest der Darstellung war alles andere als nett anzusehen: Die Darstellung war dunkel, flimmerte und nicht sonderlich schnell. Das heißt, der von mir verwendete Algorithmus ist nicht besonders effizient.Das Flimmern und die geringe Geschwindigkeit nervten mich am meisten. Da es kein Double-Buffering gab, dachte ich darüber nach, auf das Löschen des Bildschirms zu verzichten und einfach die vom Graphen eingefärbten Pixel wieder auf Schwarz zu setzen. Diese Lösung funktionierte zwar, flimmerte aber immer noch und war auch nicht schneller.

Ich machte mir Gedanken zu den von der Funktion ‘drawLine’ verwendeten Algorithmen. Waren die überhaupt effizient?

TFT_eSPI

In einem Forum fragte ich nach Alternativen zur Odroid-Go Grafikbibliothek, bekam aber keine brauchbare Antwort. Schließlich stieß ich über die Programmierung des TTGO T-Display auf die ‘TFT_eSPI‘ Bibliothek von einem Entwickler namens Bodmer. Diese unterstützt die Verwendung von Sprites, kleine rechteckige Grafikbereiche, die an beliebiger Position auf dem Display platziert werden können. In einem der zugehörigen Beispiele ist zu sehen, wie ein kleiner Graph erzeugt und gescrollt wird. Für das TTGO T-Display Board entwickelte ich mit den Sprite Funktionen eine Herzanimation, die ein stilisiertes pulsierendes Herz darstellt. Das funktionierte sehr gut.

Im Grunde funktioniert ein Sprite ähnlich wie Double-Buffering. Der Graph wird nicht auf den Bildschirm, sondern in den für das Sprite reservierten Bereich im Arbeitsspeicher geschrieben. Mit dem Befehle ‘pushSprite’ wird es dann auf das Display übertragen. Das funktionierte ziemlich gut. Auch diese Version war zwar nicht besonders schnell, aber sie flimmerte zu mindestens nicht mehr. Für diese Version darf die Odroid-Go Bibliothek nicht mehr eingebunden werden! Und darum könnt Ihr sie auch mit der Board-Einstellung ‘ESP32 Dev Module’ kompilieren.

Die TFT_eSPI Bibliothek lässt sich problemlos über den Bibliotheksverwalter der Arduino IDE installieren. Um sie an verschiedene Displays anzupassen, benötigt es allerdings einen manuellen Eingriff. Die Bibliothek bekommt die Information über das zu verwendende Display aus der Datei ‘User_Setup_Select.h’. Dort sind schon eine Fülle von Displays aufgeführt, für deren Verwendung man einfach nur die Kommentarzeichen am Anfang der jeweiligen Zeile entfernen muss. Möchte man ein völlig neues Display einbinden, so kann man die Datei ‘User_Setup.h’ in das Verzeichnis ‘User_Setups’ kopieren, dort umbennen und in ‘User_Setup_Select.h’ eine neue ‘#include’-Zeile mit dem neuen Namen anlegen. Zum Beispiel so:

//#include <User_Setup.h> // Default setup is root library folder
//#include <User_Setups/Setup_TTGO_T.h>
#include <User_Setups/Setup_Odroid_Go.h>

In der umbenannten Datei kann man anschließend alle Definitionen für das neue Display vornehmen.

Scrolling mit TFT_eSPI und Sprite

#include "Adafruit_MMA8451.h"
#include "Adafruit_Sensor.h"

#include <SPI.h>
#include <TFT_eSPI.h>

#define SDA 15
#define SCL 4

#define ARRAY_SIZE 320
#define SCROLL_WIDTH 320
#define SCROLL_HEIGHT 240

TFT_eSPI tft = TFT_eSPI();
TFT_eSprite graph1 = TFT_eSprite(&tft);

Adafruit_MMA8451 mma = Adafruit_MMA8451();

int xArray [ARRAY_SIZE];
int yArray [ARRAY_SIZE];
int zArray [ARRAY_SIZE];

int drawFirstPos = SCROLL_WIDTH - 1;
int drawLastPos = SCROLL_WIDTH - 2;
int drawYPos = SCROLL_HEIGHT / 2;
int scaleFactor = 16384 / SCROLL_HEIGHT;
int scrollPosX = (320 - SCROLL_WIDTH) / 2;
int scrollPosY = (240 - SCROLL_HEIGHT) / 2;

int arrayPos = 0;

int first = true;

int delayValue = 0;

int showTime = 0;

void setup(void) {
  Serial.begin(115200);
  Serial.println("Adafruit MMA8451 test!");

  //Set up the display
  tft.init();
  tft.setRotation(3);
  tft.fillScreen(TFT_BLACK);

  // For brightness
  ledcSetup(2, 10000, 8);
  ledcAttachPin(TFT_BL, 2);
  ledcWrite(2, 100);

  // Set up sprite
  graph1.setColorDepth(8);
  graph1.createSprite(SCROLL_WIDTH, SCROLL_HEIGHT);
  graph1.fillSprite(TFT_BLACK);

  Wire.begin(SDA, SCL);
  if (! mma.begin()) {
    Serial.println("Couldnt start");
    while (1);
  }
  Serial.println("MMA8451 found!");

  mma.setRange(MMA8451_RANGE_2_G);

  Serial.print("Range = ");
  Serial.print(2 << mma.getRange());
  Serial.println("G");
}

void loop() {
  // Measurements values read and store
  mma.read();
  xArray [arrayPos] = (int) (mma.x / scaleFactor);
  yArray [arrayPos] = (int) (mma.y / scaleFactor);
  zArray [arrayPos] = (int) (mma.z / scaleFactor);

  // Draw graph
  int actPos = arrayPos;
  int lastPos = actPos;
  if (lastPos == 0) {
    lastPos = ARRAY_SIZE - 1;
  } else {
    lastPos--;
  }
  
  if (first) {
    graph1.drawLine(arrayPos - 1, xArray [arrayPos - 1] + drawYPos, arrayPos, xArray [arrayPos] + drawYPos, TFT_YELLOW);
    graph1.drawLine(arrayPos - 1, yArray [arrayPos - 1] + drawYPos, arrayPos, yArray [arrayPos] + drawYPos, TFT_GREEN);
    graph1.drawLine(arrayPos - 1, zArray [arrayPos - 1] + drawYPos, arrayPos, zArray [arrayPos] + drawYPos, TFT_RED);
    graph1.pushSprite(scrollPosX, scrollPosY);
  } else {
    graph1.drawLine(drawFirstPos, xArray [actPos] + drawYPos, drawLastPos, xArray [lastPos] + drawYPos, TFT_YELLOW);
    graph1.drawLine(drawFirstPos, yArray [actPos] + drawYPos, drawLastPos, yArray [lastPos] + drawYPos, TFT_GREEN);
    graph1.drawLine(drawFirstPos, zArray [actPos] + drawYPos, drawLastPos, zArray [lastPos] + drawYPos, TFT_RED);
    graph1.pushSprite(scrollPosX, scrollPosY);
    graph1.scroll(-1, 0);
  }
  delay (delayValue);

  // Actualize array position
  arrayPos++;
  if (arrayPos > SCROLL_WIDTH - 1) {
    first = false;
  }
  if (arrayPos > ARRAY_SIZE - 1) {
    arrayPos = 0;
  }

}

Kommentar hinterlassen

Deine E-Mail-Adresse wird nicht veröffentlicht.