float Ausgabe begrenzen auf 1 (Nach)Kommastelle

Moin zusammen,

Ich muss mittels eines Potis eine Zeit einstellen können.
Diese soll auch auf zehntel Sekunden genau einstellbar sein.
Auch möchte ich die Zeit auf einem 4zeiligen LCD Display (2004)
darstellen.
Es klappt “soweit - so gut”. [Anzeige] 0.00 → 20.00
Frage: Wie begrenze ich float “auf eine Nachkommastelle” und/oder
die Ausgabe der Anzeige auf nur eine Nachkommastelle?

void loop()
{  // Zeitverteiler
unsigned long m = millis();
Einlesen();

if ((m - Time1 > Interval1)&&Merker==2)
{MaskeDisplay();
Merker=0;}

if (m - Time1 > Interval1)
{UpdateDisplay();
Time1 = m;
Merker++;}
}

void Einlesen()
{
valD = map(analogRead(PotiDPin), 0, 1023, 0, 200);
Anzeige=valD*0.1;
}

void MaskeDisplay()
{lcd.setCursor(0,3);        // Anfang auf Stelle 0, Zeile 3 setzen
lcd.print("Dosierzeit:");    // Text ausgeben
}               

void UpdateDisplay()
{
lcd.setCursor(12,3);       // Anfang auf Stelle 12, Zeile 3 setzen
lcd.print("     ");             // Lösche vorherige Anzeige
lcd.setCursor(12,3);      // Anfang auf Stelle 12, Zeile 3 setzen
lcd.print(Anzeige);        // Gebe Wert aus
}

Der Anfang des Sketches, auch wenn er evtl nicht von Nöten ist…

#include "Wire.h"
#include "LiquidCrystal_I2C.h"              // Library für LCD aufgerufen
LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Stelle die Adresse auf 0x3F, Setze Anschlusspins

// Variablen deklarieren

const int PotiAPin = A0;      //PotiMotor A
const int PotiBPin = A1;      //PotiMotor B
const int PotiCPin = A2;      //Poti Mischer
const int PotiDPin = A3;      //Poti Zeit
const int Trigger = 2;        //Abzug Pistole auf D2
const int Mischer = 3;        //PWM Mischer
const int MotA = 5;           //PWM Motor A
const int MotB = 6;           //PWM Motor B
const int NurA  = 7;          //Taster "Nur A"
const int NurB  = 8;          //Taster "Nur B"
const int Flush = 9;          //Taster "Spülen"
const int Piepser = 10;       //Piepser
const int LEDon =11;          //LED Einschalter
const int Warn = 12;          //Blauer Taster Licht
int valA;
int valB;
int valC;
int valD;
int Merker =2;
// Zeiten (ohne delay()) anlegen
long Time1 = millis();  // Zeit zum Updaten des Displays 
long Time2 = millis();  // Zeit bis zum Spülvorgang
long Time3 = millis();  // Dauer Spülvorgang
long Time4 = millis();;  // Dosierzeit - wird durch Poti "Zeit" * 1000 bestimmt 

long Interval1 = 700;          // Zeit bis zum LCD Update
long Interval2 = 50000;        // Zeit bis zum Spülvorgang
long Interval3 = 8000;         // Dauer Spülvorgang
long Interval4;                // Dosierzeit - wird durch Poti "Zeit" * 1000 bestimmt
float Anzeige;                 // um Sekunden auch als Komma-Zahl anzuzeigen
float AzuB;
// Warnen geht auch ohne weiteren Interval, bloß nicht "m"  zurücksetzen
void setup()
{
// initialisiere serielle Kommunikation mit Rechner
Serial.begin(9600); 
lcd.begin(20,4);   // initialisiere lcd für 20 Chars 4 Linien
lcd.backlight();  // LCD Hintergrundbeleuchtung aktivieren
lcd.clear();      // LCD löschen

pinMode(Trigger, INPUT_PULLUP);
pinMode(MotA, OUTPUT);  
pinMode(MotB, OUTPUT);  
pinMode(Mischer, OUTPUT);  
pinMode(NurA, INPUT_PULLUP);
pinMode(NurB, INPUT_PULLUP);
pinMode(Flush, INPUT_PULLUP);
pinMode(Piepser, OUTPUT);
pinMode(LEDon, OUTPUT);
pinMode(Warn, OUTPUT);

analogWrite(MotA, 0);  //set motor A to run at (0/255 = 0)% duty cycle (stop)
analogWrite(MotB, 0);  //set motor B to run at (0/255 = 0)% duty cycle (stop)
analogWrite(Mischer, 0);  //set motor Mixer to run at (0/255 = 0)% duty cycle (stop)
}

Danke für Eure Infos und dass Ihr hier seid :slight_smile:

Stefan

Das wäre ganz einfach:

lcd.print(Anzeige,1);        // Gebe Wert aus
1 Like
long Time1 = millis();  // Zeit zum Updaten des Displays
long Time2 = millis();  // Zeit bis zum Spülvorgang
long Time3 = millis();  // Dauer Spülvorgang
long Time4 = millis();  // Dosierzeit - wird durch Poti "Zeit" * 1000 bestimmt

Du weisst doch was die Timestamps bedeuten, warum gibst du ihnen keine vernünftigen Namen?
Das würde den Kommentar größtenteils überflüssig machen.

Wozu diese komplizierte Initialisierung mit 0?
Eine bloße Deklaration hat exakt die gleiche Wirkung.

Warum sind deine Zeitvariablen keine unsigned longs?
Soll das nicht bei den Überläufen von millis funktionieren?

Bei weiterer Betrachtung des Kodes

long Interval1 = 700;          // Zeit bis zum LCD Update
long Interval2 = 50000;        // Zeit bis zum Spülvorgang
long Interval3 = 8000;         // Dauer Spülvorgang
long Interval4;                // Dosierzeit - wird durch Poti "Zeit" * 1000 bestimmt

zeigt sich, dass die Kommentare im ersten Block einfach völlig falsch sind.

Und hier wieder:

Du weisst doch was die Variablen bedeuten, warum gibst du ihnen keine vernünftigen Namen?
Das würde den Kommentar größtenteils überflüssig machen.

Warum sind deine Zeitvariablen keine unsigned longs?
Soll das nicht bei den Überläufen von millis funktionieren?

DerLehmi:
Das wäre ganz einfach:

lcd.print(Anzeige,1);        // Gebe Wert aus

Scheiße... das geht ja ???
Danke für den Tipp !!!

Whandall:

long Time1 = millis();  // Zeit zum Updaten des Displays

long Time2 = millis();  // Zeit bis zum Spülvorgang
...



Du weisst doch was die Timestamps bedeuten, warum gibst du ihnen keine vernünftigen Namen?
Das würde den Kommentar größtenteils überflüssig machen.

a) ich habe mir das abgeschaut, bin noch nicht so sicher auf den OC-Beinen
und bin schon froh, dass ich nicht mehr mit delay() arbeite :wink:
b) Weil ich die Zeiten im Verlauf der Programmierarbeit für
teils mehrere Aktionen einsetzen werden ( ähnlich dem
Sprungverteiler MakeDisplay() und UpdateDisplay() Dann ist eine
eindeutige Benennung mglw. nicht mehr machbar.

Whandall:
Wozu diese komplizierte Initialisierung mit 0?
Eine bloße Deklaration hat exakt die gleiche Wirkung.

Weil ich es noch nicht besser kann. Aber danke für den Tipp !

Whandall:
Warum sind deine Zeitvariablen keine unsigned longs?
Soll das nicht bei den Überläufen von millis funktionieren?

Gute Frage. Danke für den Hinweis! Wird geändert.

Jungs, bitte nicht zu viel erwarten... ich übe noch!

Stefan

StefanBoth:
b) Weil ich die Zeiten im Verlauf der Programmierarbeit für
teils mehrere Aktionen einsetzen werden

Das schlag dir besser aus dem Kopf, jeder mit millis gesteuerte Ablauf benötigt einen eigenen Timestamp.
Im Moment mag ja noch sicher sein, dass sich manches nicht überlappt, es ist aber besser alles
so zu konstruieren, dass es sich jederzeit überlappen kann.

Deine Punkte sind alles keine Gründe für die grottenschlechten Namen und die falschen Kommentare.
Jetzt musst du nur noch die Verwendung der Variablen falsch dokumentieren und hast keine Chance
mehr Fehler zu sehen.

Betrachte das nicht als Nörgelei, das sind nur gut gemeinte Tips.

Hey Whandall,

Ich denke, wir stehen ziemlich weit auseinander auf der "C-Programmieren-können-Skala".
Du siehst JETZT bereits Probleme kommen, von denen ich noch nichts ahne.
Es ist bestimmt gut gemeint von Dir, mich darauf aufmerksam zu machen, kein Zweifel.

Es ist nur... ich habe (noch) keine Ahnung wovon Du überhaupt redest. Somit läuft Dein Tipp ins Leere.

Ich fürchte, ich muss erst vor die Wand laufen - um zu erkennen, dass dort eine ist.
Daher nimm mir meine Ignoranz nicht übel. Ich weiß es noch nicht besser.

Wenn ich mir meine ersten Programme anschaue, überkommt mich bereits heute das kalte Grauen.
Folglich werde ich Stück für Stück besser. Gib mir bitte noch ein wenig Zeit. Ich habe das
nie gelernt - und mich einfach reingestürzt.

Grüße

Stefan

Du würdest also einem Fahrradanfänger nicht empfehlen, nicht umgekehrt auf's Fahrrad zu steigen?

Lieber ihn sich erst hinlegen lassen und amüsiert zuschauen?

Sinnvolle Namensgebung und korrekte Kommentare sind nicht abhängig vom Beherrschen
einer Programmiersprache, beides erleichtert aber extrem das Programmieren und das (Wieder-)Verstehen,
speziell zwei bis drei Jahre nachdem du den Kode geschrieben hast.

Den Effekt mit den gruseligen Anfangsprogrammen habe auch ich erlebt,
das Arduino Umfeld ist in mancher Hinsicht schon sehr speziell, C konnte ich schon vorher.

Ich würde dem Fahrradfahrer (wenn er mich danach fragt) ZEIGEN
wie man sich richtig aufs Fahrrad setzt. Dadurch vermeidet man auch Missverständnisse
wo vorne und hinten ist. (Um bei Deinem Beispiel zu bleiben).

Ein:
"das geht so nicht - dann fällst du hin" ist in der Sache richtig.
Dann weiß aber der Anfänger noch nicht, "WIE" es geht,
und motiveren ist auch was anderes.

Ich fürchte, die Aussicht von Deinem Standpunkt aus gesehen ist
hervorragend. Du stehst bereits an einer Stelle, die Übersicht bietet.

Komm einfach mal "zu mir herunter" und entdecke, dass ich nicht
bis zum nächsten Baum schauen kann - geschweige bis zum nächsten Gipfel.
Lass mir Zeit, Probleme und deren Folgen zu erkennen. Ich arbeite dran.

Versteh mich nicht falsch DANKE für Eure Hilfe !!! ohne dieses
Forum hätte ich keine Chance. Es würde mich freuen, wenn ich meine
(teils gewiss dusseligen Fragen) weiter stellen darf, OHNE dass ihr
verzweifelt !

Stefan

StefanBoth:
Ich würde dem Fahrradfahrer (wenn er mich danach fragt) ZEIGEN
wie man sich richtig aufs Fahrrad setzt. Dadurch vermeidet man auch Missverständnisse
wo vorne und hinten ist. (Um bei Deinem Beispiel zu bleiben).

Warten wir ab ob du das besser verstehst, ich habe nicht alles ordentlich machen können,
da sich der Sinn nicht aus den Variablen alleine ergibt.
Die Dosierpumpe kann ich immerhin kontrollieren.

Floats braucht man nicht, habe ich entfernt, der Wert in zehntelSekunden reicht völlig.

#include "Wire.h"
#include "LiquidCrystal_I2C.h"

// Stelle die Adresse auf 0x3F, Setze Anschlusspins
LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

const byte PotiAPin = A0;      //PotiMotor A
const byte PotiBPin = A1;      //PotiMotor B
const byte PotiCPin = A2;      //Poti Mischer
const byte pinDosierZeitPoti = A3;
const byte Trigger = 2;        //Abzug Pistole auf D2
const byte Mischer = 3;        //PWM Mischer
const byte MotA = 5;           //PWM Motor A
const byte MotB = 6;           //PWM Motor B
const byte NurA  = 7;          //Taster "Nur A"
const byte NurB  = 8;          //Taster "Nur B"
const byte Flush = 9;          //Taster "Spülen"
const byte Piepser = 10;       //Piepser
const byte LEDon = 11;         //LED Einschalter
const byte Warn = 12;          //Blauer Taster Licht

int dosierzeitZehntel;
int dosierzeitZehntelOnDisplay;

unsigned long lastDisplayUpdate;
unsigned long displayUpdateInterval = 700;
unsigned long dosierungGestartet;
unsigned long dosierungsInterval;

unsigned long vorspuelenGestartet;
unsigned long spuelenGestartet;
unsigned long intervalVorDemSpuelen = 50000;
unsigned long dauerSpuelvorgang = 8000;

void setup() {
  Serial.begin(250000);
  lcd.begin(20, 4);  // 20 Zeichen 4 Zeilen
  lcd.backlight();
  lcd.clear();

  pinMode(Trigger, INPUT_PULLUP);
  pinMode(MotA, OUTPUT);
  pinMode(MotB, OUTPUT);
  pinMode(Mischer, OUTPUT);
  pinMode(NurA, INPUT_PULLUP);
  pinMode(NurB, INPUT_PULLUP);
  pinMode(Flush, INPUT_PULLUP);
  pinMode(Piepser, OUTPUT);
  pinMode(LEDon, OUTPUT);
  pinMode(Warn, OUTPUT);

  //  analogWrite(MotA, 0);     // Output-Pins starten mit 0
  //  analogWrite(MotB, 0);
  //  analogWrite(Mischer, 0);

  liesDosierzeit();
  MaskeDosierzeit();
  AnzeigeDosierzeit();
}

void loop() {
  unsigned long topLoop = millis();
  liesDosierzeit();
  bool neueDosierzeit = dosierzeitZehntelOnDisplay != dosierzeitZehntel;
  // wenn es Zeit ist oder sich der Wert geändert hat
  if (topLoop - lastDisplayUpdate > displayUpdateInterval || neueDosierzeit) {
    if (neueDosierzeit) {
      AnzeigeDosierzeit();
      dosierzeitZehntelOnDisplay = dosierzeitZehntel;
    }
    lastDisplayUpdate = topLoop;
  }
  // wenn die Dosierpumpe läuft und sie schon lange genug lief...
  if (dosierungsInterval && topLoop - dosierungGestartet > dosierungsInterval) {
    dosierungsInterval = 0;
    // Dosierpumpe aus
  }
}

void liesDosierzeit() {
  dosierzeitZehntel = map(analogRead(pinDosierZeitPoti), 0, 1023, 0, 200);
}

void MaskeDosierzeit() {
  lcd.setCursor(0, 3);
  lcd.print(F("Dosierzeit:"));
}

void AnzeigeDosierzeit() {
  lcd.setCursor(13, 3);
  if (dosierzeitZehntel < 100) {  // nicht existierende 10er Stelle ?
    lcd.write(' ');             // dann sicherheitshalber löschen
  }
  lcd.print(dosierzeitZehntel / 10);  // die Stellen vor dem Komma
  lcd.write('.');
  lcd.print(dosierzeitZehntel % 10);  // die Stelle nach dem Komma
}

void starteDosierungMit(unsigned int zehntelSekunden) {
  dosierungGestartet = millis();
  dosierungsInterval = zehntelSekunden * 100UL;
  // Dosierpumpe ein
}

Hallo Whandall,

Du hast dir da echt Mühe gegeben - vielen Dank! Und ich habe statt 20 Fragezeichen nur noch zwei im Gesicht - das ist doch schon mal ein Erfolg. :slight_smile:
Ich denke mein größtes Problem ist das unstrukturierte arbeiten. Da wäre es sicher hilfreich gewesen ich hätte irgendwann irgendeine Programmiersprache gelernt.
Aber mit jedem höheren Level kommt auch der größere Überblick.
Danke für deine Hilfe. Das wird schon.

Stefan

Die Namen sind jetzt vielleicht etwas geschwätzig,
aber der Kode wird durch aussagekräftige Namen doch deutlich besser zu lesen, findest du nicht?

Auf floats kann man in den meisten Fällen verzichten, ganze Zahlen lassen sich genauso als Bruchteile
interpretieren, sind genauer, schneller zu verarbeiten und der resultierende Kode ist viel kleiner.

Das LCD Display nur zu beschreiben, wenn sich der Wert geändert hat minimiert das Flackern.
Man könnte das noch weiter treiben, indem man nur geänderte Zeichen überträgt,
das ist aber deutlich aufwändiger.

Da ist so viel "Neues" drin, dass mir Angst und Bange wird.
Ich versuche gerade mich durchzukämpfen. Ich wette jetzt
schon, dass Du nicht zufrieden sein wirst.
Wohlan...

if (topLoop - lastDisplayUpdate > displayUpdateInterval || neueDosierzeit) {

Das "|| neueDosierzeit" führt dazu, dass das Display ständig geupdatet wird, wenn der Poti
"zwischen zwei Werten steht". Man müsste die Werteänderung auf "mindestens 0,2" festlegen.

Später - sonst komme ich vor lauter "ich verstehe den Code nicht" nicht weiter.

Das mit der Zehntel-Anzeige ist Klasse.
Werde ich sofort übernehmen.

Stefan

lcd.print(F("Dosierzeit:"))

Wozu ist das "F" gut?

Stefan

Der Text "Dosierzeit: " steht dann nicht im RAM, sondern nur im Flash.

__FlashStringHelper

http://playground.arduino.cc/Learning/Memory

Ich halte damit also RAM frei ? Sind ja nur zwei Kilobyte.
Allerdings belaste ich den Flash Speicher zusätzlich mit dem Programmteil der dies ermöglicht.
Ist ja wie früher beim C64. Nichts ist so gut, dass es nicht auch noch Nachteile hätte.

Stefan

RAM ist knapper als Flash. Die konstanten Texte stehen sowieso immer im Flash, da verlierst du nichts.
Wie viele Bytes die Aufrufe mehr brauchen, müsstest du im Einzelfall testen.

Der Grund weshalb String Konstanten normalerweise am Anfang ins RAM kopiert werden liegt an der Prozessor Architektur. Es ist eine Harvard Architektur mit getrennten Adress-Räumen und Bussen für Instruktionen und Daten. Das heißt eine normale Anweisung kann nicht in den Speicher greifen in dem das Programm steht. Sie erwarte die Daten im RAM.
Der Zugriff auf das ROM ist natürlich möglich, aber nur sehr eingeschränkt. Und dazu dienen dann Makros wie F() und ähnliches die das über Umwege (z.B. byte-weises Auslesen aus dem Flash) erledigen

Serenifly:
Der Zugriff auf das ROM ist natürlich möglich, aber nur sehr eingeschränkt. Und dazu dienen dann Makros wie F() und ähnliches die das über Umwege (z.B. byte-weises Auslesen aus dem Flash) erledigen

F() erzeugt keinen zusätzlichen Kode, oder spezielle Instruktionen
(außer den Definitionen die die String Konstante in PROGMEM platzieren).
Es erlaubt dem Kompiler lediglich char* auf den RAM von char* in den Flash zu unterscheiden.
Das führt dann z.B. bei Serial.print() dazu, dass die Bytes aus dem Flash und nicht aus dem RAM geholt werden.