Gelesene Messwerte mit variabler Range auf ein Oled Display skalieren

Hallo zusammen,

habe mich gerade erst hier angemeldet.

Ich habe schon einiges an Arduino Projekten umgesetzt und ein wenig Erfahrung.

Folgende Komponenten habe ich im Einsatz:

Arduino Nano
4x GY-302 BH1750
PCA9548A I2C 8-Kanal-Multiplexer
AZDelivery 3 x 1,3 Zoll OLED Display I2C SSH1106 Chip 128 x 64 Pixel I2C

Ich lese die Sensoren aus und speichere mir die werte im Achsbezug x+/x- || y+/y-

Auf dem Display zeichne ich zentrisch eine horizontale sowie vertikale Linie als Achs / Zentrumsbezug.

Um die aktuelle Ausrichtung / anstehende Korrekturrichtung anzuzeigen zeichne ich eine runde, gefüllte Ellipse die ständig in ihrer x/y Position korrigiert wird.

Nur beim skalieren der Messwerte im Bezug auf Display Höhe / Breite steh ich auf dem Schlauch

Bildschirm Bereich habe ich ermittelt und verrechnet.

Breite / bzw. Höhe - 2 * R der „Ellipse“ Kreis

Frage:

Hat mir jemand einen Tipp wie ich die Max Werte ( Sensorpaar x Achse bzw y Achse auf die Bildschirmbreite / Höhe skalieren kann?

So dass sich mein „wandernder Punkt“ in diesem Bereich Aufhält und den anzeigbaren Bereich nicht verlässt?

Vielen Dank im Voraus.

Grüße Markus

Moin @fullforce_1988 ,

könntest Du Deine Frage vielleicht mit einer Skizze verdeutlichen? Ein Bild sagt mehr als tausend Worte ... :wink:

Habe meinen Thema um eine Handskizze erweitert.

Ok, danke!

Die Funktion map() eignet sich hierfür beispielsweise:

https://docs.arduino.cc/language-reference/de/funktionen/math/map/

map(value, fromLow, fromHigh, toLow, toHigh)

value: Die Nummer, die zugeordnet werden soll.
fromLow: Die untere Grenze des aktuellen Wertebereichs.
fromHigh: Die obere Grenze des aktuellen Wertebereichs.
toLow: Die untere Grenze des Zielbereichs des Werts. (z.B. linker Bildschirmrand Pixel 0)
toHigh: Die obere Grenze des Zielbereichs des Werts. (z.B. rechter Bildschirmrand Pixel 127)

Die Funktion kann auch mit negativen Zahlen umgehen ...

Gerne mal ausprobieren!
Viel Erfolg
ec2021

Super :+1:t2:
Das schaut schon vielversprechend aus.

Werde ich testen.

Danke dir :smiley:

Gerne, hier auch mal zum Ausprobieren:

https://wokwi.com/projects/422698408752327681

/*
  Forum: https://forum.arduino.cc/t/gelesene-messwerte-mit-variabler-range-auf-ein-oled-display-skalieren/1353190
  Wokwi: https://wokwi.com/projects/422698408752327681

  ec2021

*/
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"

constexpr byte TFT_DC {9};
constexpr byte TFT_CS {10};
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);

constexpr byte potiX {A0};
constexpr byte potiY {A1};
constexpr int radius {20};

unsigned long lastTime = 0;
int dispWidth;
int dispHeight;

class CircleClass {
  private:
    int _x;
    int _y;
    int _rad = -1;
    void paint(uint16_t color) {
      tft.drawCircle(_x, _y, _rad, color);
    }
  public:
    void set(int dx, int dy, int rad) {
      if (_rad > -1) {
        paint(ILI9341_BLACK);
      }
      _x = dx;
      _y = dy;
      _rad = rad;
      paint(ILI9341_RED);
    }
};

CircleClass c1;


void setup() {
  Serial.begin(115200);
  Serial.println("Graphics Example");
  tft.begin();
  tft.setRotation(1);
  dispWidth = tft.width();
  dispHeight = tft.height();
  Serial.println(dispWidth);
  Serial.println(dispHeight);
  clearScreen();
}


void loop(void) {
  if (millis() - lastTime > 100) {
    lastTime = millis();
    handleData();
  }
}

void clearScreen() {
  tft.fillScreen(ILI9341_BLACK);
}

void handleData() {
  int val = analogRead(potiX);
  int xPos = map(val,0,1023,radius,dispWidth-radius);
  val = analogRead(potiY);
  int yPos = map(val,0,1023,radius,dispHeight-radius);
  c1.set(xPos,yPos,radius);
}

Schaltung:

Die Werte der beiden Potis werden auf die x bzw. y Position eines Kreises gemappt:

  int val = analogRead(potiX);
  int xPos = map(val,0,1023,radius,dispWidth-radius);
  val = analogRead(potiY);
  int yPos = map(val,0,1023,radius,dispHeight-radius);

Der Radius des Kreises wird von den Displaygrenzen abgezogen, damit sie komplett auf dem Schirm bleiben. Das begrenzt natürlich die nutzbare Fläche des Displays.

Die Werte dispWidth und dispHeight werden hier vom Displaytreiber genommen, das kann man bei gegebenem Display natürlich auch als Konstanten auslegen. Dabei ist ggf. auf die Ausrichtung (Rotation) des Displays zu achten (aufrecht oder quer).

Hallo @ec2021 ,

ich hatte die map Funktion ausprobiert, leider brachte sie nicht den gewünschten Erfolg.
Die Messwerte wurden zwar sauber skaliert, hatte aber ein rastendes verhalten zur Folge.
Sprich keine flüssige Bewegung des Punktes.

Die Funktion hat mir dann aber indirekt doch geholfen, da ich mir ihren Code angesehen habe. Darauf hin habe ich mir die Formeln je Sensorseite & Bedingung erstellt.

Ich denke, einiges kann man bestimmt noch schöner oder eleganter umsetzen.
Aber die Funktion was ich benötige ist somit vorhanden.

Schwellwerte lassen sich sauber einstellen und habe ein robustes Steuersignal.
Vor diesem Projekt hatte ich eine Analogwert Ermittlung & Auswertung in einer Logo.
Dies brachte aber einige Probleme mit sich...

Hier der gesamte Code sowie ein paar Bilder:

#include <U8g2lib.h>
#include <BH1750.h>
#include <Wire.h>


#define TCAADDR 0x70

#define relais1 2
#define relais2 3
#define relais3 4
#define relais4 5

BH1750 bh1750_1;
BH1750 bh1750_2;
BH1750 bh1750_3;
BH1750 bh1750_4;

U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE);

void TCA9548A(uint8_t bus) {
  Wire.beginTransmission(0x70);
  Wire.write(1 << bus);
  Wire.endTransmission();
}


void u8g2_prepare() {
  u8g2.begin();
  u8g2.enableUTF8Print();
  u8g2.setFont(u8g2_font_logisoso32_tf);
}


void kreis_zeichnen(float x, float y, float r) {
  TCA9548A(0);
  u8g2.setFont(u8g2_font_logisoso32_tf);
  u8g2.setFontDirection(0);
  u8g2.clearBuffer();
  u8g2.drawLine(0, 32, 128, 32);
  u8g2.drawLine(64, 0, 64, 64);
  u8g2.drawFilledEllipse(x, y, r, r);
  u8g2.sendBuffer();
}


void ausgabe(float sensor_x_p, float sensor_x_m, float sensor_y_p, float sensor_y_m) {

  const int verz = 15;
  float x, y, max_x, max_y, min_x, min_y, toleranz_x, toleranz_y, r, tolprozent;

  r = 8;
  tolprozent = 10;  // display
  toleranz_x = ((sensor_x_p + sensor_x_m) / 100) * tolprozent;
  toleranz_y = ((sensor_y_p + sensor_y_m) / 100) * tolprozent;

  Wire.begin();
  Serial.begin(9600);

  if (sensor_x_p > sensor_x_m) {
    max_x = sensor_x_p;
    min_x = sensor_x_m;
    x = ((min_x + (max_x - min_x)) / ((max_x + min_x) / 128)) - r;
    Serial.print(" X:");
    Serial.print(x);
    Serial.println();
  } else {
    max_x = sensor_x_m;
    min_x = sensor_x_p;
    x = ((max_x - (max_x - min_x)) / ((max_x + min_x) / 128)) + r;
    Serial.print(" X:");
    Serial.print(x);
    Serial.println();
  }


  if (sensor_y_p > sensor_y_m) {
    max_y = sensor_y_p;
    min_y = sensor_y_m;
    y = ((min_y + (max_y - min_y)) / ((max_y + min_y) / 64)) - r;
    Serial.print(" Y:");
    Serial.print(y);
    Serial.print(" ");
  } else {
    max_y = sensor_y_m;
    min_y = sensor_y_p;
    y = ((max_y - (max_y - min_y)) / ((max_y + min_y) / 64)) + r;
    Serial.print(" Y:");
    Serial.print(y);
    Serial.println();
  }

  if ((max_x - min_x <= toleranz_x) && (max_y - min_y <= toleranz_y)) {
    x = 64;
    y = 32;
  }

  kreis_zeichnen(x, y, r);


  tolprozent = 20;  // Relais
  toleranz_x = ((sensor_x_p + sensor_x_m) / 100) * tolprozent;
  toleranz_y = ((sensor_y_p + sensor_y_m) / 100) * tolprozent;


  // Segel ändert Lage nicht
  if ((max_x - min_x <= toleranz_x) && (max_y - min_y <= toleranz_y)) {
    delay(verz);
    digitalWrite(relais1, HIGH);
    delay(verz);
    digitalWrite(relais2, HIGH);
    delay(verz);
    digitalWrite(relais3, HIGH);
    delay(verz);
    digitalWrite(relais4, HIGH);
    delay(verz);
  }
  // Segel im Uhrzeigersinn drehen
  else if (sensor_x_m > sensor_x_p && (sensor_x_p - sensor_x_m) > toleranz_x) {
    digitalWrite(relais1, HIGH);
    delay(verz);
    digitalWrite(relais2, LOW);
    delay(verz);
    digitalWrite(relais3, HIGH);
    delay(verz);
    digitalWrite(relais4, HIGH);
    delay(verz);
  }
  // Segel gegen den Uhrzeigersinn drehen
  else if (sensor_x_p > sensor_x_m && (sensor_x_p - sensor_x_m) > toleranz_x) {
    digitalWrite(relais1, LOW);
    delay(verz);
    digitalWrite(relais2, HIGH);
    delay(verz);
    digitalWrite(relais3, HIGH);
    delay(verz);
    digitalWrite(relais4, HIGH);
    delay(verz);
  }
  // Segel nach oben kippen
  else if (sensor_y_p > sensor_y_m && (sensor_y_p - sensor_y_m) > toleranz_y) {
    digitalWrite(relais1, HIGH);
    delay(verz);
    digitalWrite(relais2, HIGH);
    delay(verz);
    digitalWrite(relais3, LOW);
    delay(verz);
    digitalWrite(relais4, HIGH);
    delay(verz);
  }
  // Segel nach unten kippen
  else {  //(sensor_y_m > sensor_y_p && (sensor_y_p - sensor_y_m) > toleranz_y) {
    digitalWrite(relais1, HIGH);
    delay(verz);
    digitalWrite(relais2, HIGH);
    delay(verz);
    digitalWrite(relais3, HIGH);
    delay(verz);
    digitalWrite(relais4, LOW);
    delay(verz);
  }
}


void setup() {

  Wire.begin();
  Serial.begin(9600);

  pinMode(relais1, OUTPUT);
  pinMode(relais2, OUTPUT);
  pinMode(relais3, OUTPUT);
  pinMode(relais4, OUTPUT);

  digitalWrite(relais1, HIGH);
  digitalWrite(relais2, HIGH);
  digitalWrite(relais3, HIGH);
  digitalWrite(relais4, HIGH);

  TCA9548A(0);
  u8g2.begin();
  u8g2_prepare();

  delay(10);
  TCA9548A(1);
  bool communication1 = bh1750_1.begin(BH1750::CONTINUOUS_HIGH_RES_MODE_2, 0x23);
  delay(10);
  TCA9548A(2);
  bool communication2 = bh1750_2.begin(BH1750::CONTINUOUS_HIGH_RES_MODE_2, 0x23);
  delay(10);
  TCA9548A(3);
  bool communication3 = bh1750_3.begin(BH1750::CONTINUOUS_HIGH_RES_MODE_2, 0x23);
  delay(10);
  TCA9548A(4);
  bool communication4 = bh1750_4.begin(BH1750::CONTINUOUS_HIGH_RES_MODE_2, 0x23);
  delay(10);
}


void loop(void) {
  const int verz = 5;

  float sensor_x_p, sensor_x_m;
  float sensor_y_p, sensor_y_m;
  float toleranz_x, toleranz_y;

  TCA9548A(1);
  delay(verz);
  sensor_x_p = (bh1750_1.readLightLevel() / 1.2) / 2;

  TCA9548A(2);
  delay(verz);
  sensor_x_m = (bh1750_2.readLightLevel() / 1.2) / 2;

  TCA9548A(3);
  delay(verz);
  sensor_y_p = (bh1750_3.readLightLevel() / 1.2) / 2;

  TCA9548A(4);
  delay(verz);
  sensor_y_m = (bh1750_4.readLightLevel() / 1.2) / 2;

  ausgabe(sensor_x_p, sensor_x_m, sensor_y_p, sensor_y_m);
}





Danke für deine Hilfe.
Grüße

Freut mich, dass Du eine Lösung gefunden hast!

Wenn die Rückgabe der map-Funktion ein rastendes Verhalten zeigt, liegt es aber wohl eher an den Parametern oder den Eingabedaten als am Algorithmus... :wink:

Das kann daran liegen, dass der Input Sprünge aufweist. Das wäre das Offensichtliche.

Oder aber die Skalierung der Eingangsdaten ist erkennbar "gröber" als die der Ausgangsdaten...

Wenn (überspitzt dargestellt), Werte von 0 bis 10 auf Werte von 0 bis 100 abgebildet werden, ist das Ergebnis natürlich nicht kontinuierlich sondern durch Sprünge gekennzeichnet.

Nur zur "Ehrenrettung" der map Funktion... :wink:

Weiter viel Erfolg!
Gruß
ec2021

Hallo,

man kann Sprünge weich gestalten, indem man den jeweils neu berechneten Wert als Zielwert betrachtet und jeweils den alten Zielwert zum neuen Zielwert "sanft" mittels +1 oder -1 heranführt. Kann man auch noch zeitlich verzögert machen wenn die Optik perfekt sein soll. Die eigentliche Rasterung bestimmt dann nur noch die Auflösung des Displays.

Mir sind noch zwei Dinge aufgefallen:

Die Zeilen

  Wire.begin();
  Serial.begin(9600);

in der Funktion ausgabe() sind vermutlich noch Copy'n Paste-Überbleibsel...

Und durch die Umrechnung in Lux ( Division durch 1.2) sowie Halbierung der Sensorwerte (Division durch 2) "verschwindet" ein Teil Deiner Messwerte hinter dem Komma der float Variablen. Das führt bei Umwandlung in eine Ganzzahl (long) ggf. dazu, dass Sprünge auftreten, weil es letztlich zu Lasten der Auflösung geht. Nur so als Tipp...

Nachtrag: Um das Problem zu umgehen, kann man float-Werte vor der Umwandlung mit z.B. 100 multiplizieren, dann umwandeln und die Berechnungen durchführen, und das Ergebnis bei Bedarf wieder entsprechend rückskalieren.

Beispiel:

1,99 Euro mal 1000 = 1990,00 Euro wenn man in float bleibt

1,99 Euro wird bei Ganzzahlumrechnung zu 1 Euro. 1 Euro mal 1000 = :wink:

Also entweder in float bleiben oder gleich in Cent rechnen.

Welcher Datentyp der optimale ist, hängt dabei von den Berechnungen und dem internen Wertebereich der Datentypen ab. float hört sich manchmal toll an, hat aber auch seine Tücken.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.