Stepper-Motor zu Programmstart in Startposition fahren

Nun will ich mal mein Programm beschreiben, die Fragen, die aufgekommen sind und wie ich sie gelöst habe:

Ziel: Der Wert von mehreren Temperatur-Sensoren soll auf einer kleinen Webseite ausgegeben und in einem festen Zyklus aktualisiert werden. Die Temperatursensoren messen die Zuluft und Abluft unserer Lünftungsanlage jeweils vor dem Wärmetauscher. Der Wärmetauscher besitzt eine Bypass-Klappe, mit dem der Wärmetauscher umgangen werden kann.

Über ein kleines Regelwerk soll der ESP32 einen Schrittmotor ansteuern, der dann je nach Temperatur und Regel die Bypassklappe öffnet oder schließt. Da zu Programmstart das Programm nicht weiß, ob die Klappe geschlossen oder geöffnet ist oder der Schrittmotor gar in irgendeiner Position zwischen geschlossen und geöffnet steht, muss der Schrittmotor zu Programmstart einen Endtaster anfahren, der sich in der Position "geschlossen" befindet. Diese Position wird dann als Nullposition gesetzt.

Controller: ESP32 Wroom
Sensoren: 3 x DS18B20
Motor: N42HS3418-24B22
Motortreiber: A4988

Hilfebeiträge dazu von mir erstellt waren:

Basis-Programme:

  • Zum Auslesen der Sensor-IDs habe ich den Beispiel-Sketch von ESP32 with Multiple DS18B20 Temperature Sensors | Random Nerd Tutorials verwendet. Damit werden die Sensor-IDs im Serial Monitor ausgegeben. Am besten die Sensoren durchnummerieren, mit Silber-Edding auf den Sensoren die Nummer markieren. Vorteil der digitalen Sensoren ist, dass alle parallel an einem GPIO angeschlossen und über ihre Adresse getrennt ausgelesen werden können.
  • Als Web-Server habe ich den Advanced Webserver von Majenko Technologies verwendet, der in der Arduino IDE zur Verfügung steht.
  • Für die Sensoren sind die Bibliotheken OneWire und DallasTemperature einzubinden.

Das Programm
Hier nun vorab der vollständige Programmcode. Danach werde ich auf die Realisierung der Startposition zu Programmstart eingehen:

/*
   Copyright (c) 2015, Majenko Technologies
   All rights reserved.

   Redistribution and use in source and binary forms, with or without modification,
   are permitted provided that the following conditions are met:

 * * Redistributions of source code must retain the above copyright notice, this
     list of conditions and the following disclaimer.

 * * Redistributions in binary form must reproduce the above copyright notice, this
     list of conditions and the following disclaimer in the documentation and/or
     other materials provided with the distribution.

 * * Neither the name of Majenko Technologies nor the names of its
     contributors may be used to endorse or promote products derived from
     this software without specific prior written permission.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
   ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
   ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <AccelStepper.h>

const int DIR = 27;
const int STEP = 14;

const char *ssid = "xxxxxxxxxx";
const char *password = "yyyyyyyyyyyyy";

#define motorInterfaceType 1
AccelStepper motor(motorInterfaceType, STEP, DIR);

WebServer server(80);

const int led = 13;
	
// Data wire is connected to GPIO15
#define ONE_WIRE_BUS 15
// Setup a oneWire instance to communicate with a OneWire device
OneWire oneWire(ONE_WIRE_BUS);
// Pass our oneWire reference to Dallas Temperature sensor 
DallasTemperature sensors(&oneWire);

DeviceAddress sensor1 = { 0x28, 0x41, 0x6B, 0x49, 0xF6, 0x4D, 0x3C, 0x1D };
DeviceAddress sensor2 = { 0x28, 0x84, 0x63, 0x49, 0xF6, 0x54, 0x3C, 0x9 };
DeviceAddress sensor3 = { 0x28, 0x86, 0x57, 0x49, 0xF6, 0xC3, 0x3C, 0x51 };

float temperatur1 = sensors.getTempC(sensor1);
float temperatur2 = sensors.getTempC(sensor2);
float temperatur3 = sensors.getTempC(sensor3);
char bypassTxt[20];

unsigned long previousMillis = 0; 
const long interval = 5000;
const byte homeButton = 23;
byte hBval;   // new variable
byte ButtonInit = 0;
 

void handleRoot() {
  digitalWrite(led, 1);
  char temp[400];
  int sec = millis() / 1000;
  int min = sec / 60;
  int hr = min / 60;
  
  snprintf(temp, 400,
  "<html>\
  <head>\
    <meta http-equiv='refresh' content='5'/>\
    <title>Lueftungsklappensteuerung</title>\
    <style>\
      body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
    </style>\
  </head>\
  <body>\
    <h1>Bypassklappenstatus</h1>\
    <p>Temperatur 1: %2.1f</p>\
    <p>Temperatur 2: %2.1f</p>\
    <p>Temperatur 3: %2.1f</p>\
    <p>Bypassklappe: %12s</p>\    
  </body>\
</html>",

           temperatur1, temperatur2, temperatur3, bypassTxt
          );
  server.send(200, "text/html", temp);
  digitalWrite(led, 0);
}

void handleNotFound() {
  digitalWrite(led, 1);
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";

  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }

  server.send(404, "text/plain", message);
  digitalWrite(led, 0);
}

void setup(void) {
  pinMode(led, OUTPUT);
  digitalWrite(led, 0);
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println("");

  Serial.begin(115200);
  motor.setMaxSpeed(500);
  motor.setAcceleration(60);
  motor.setSpeed(50);


  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  if (MDNS.begin("esp32")) {
    Serial.println("MDNS responder started");
  }

  server.on("/", handleRoot);
  server.on("/inline", []() {
  server.send(200, "text/plain", "this works as well");
  });
  server.onNotFound(handleNotFound);
  server.begin();
  Serial.println("HTTP server started");
  hBval = digitalRead(homeButton);
  Serial.print("Homeschleife Button = ");
  Serial.println(hBval);
  while ((hBval == 0) && (ButtonInit == 0)) {
     motor.move(-5);
     motor.run();
     hBval = digitalRead(homeButton);
     Serial.print("Homeschleife Button = ");
     Serial.println(hBval);
     motor.setCurrentPosition(0);
  }
ButtonInit = 1;
}

void loop(void) {
  server.handleClient();

hBval = digitalRead(homeButton);  
Serial.print("Homeschleife Button = ");
Serial.println(hBval);


unsigned long currentMillis = millis(); 
if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;  
    Serial.print("Requesting temperatures...");
    sensors.requestTemperatures(); // Send the command to get temperatures
    Serial.print("Sensor 1(*C): ");
    temperatur1 = sensors.getTempC(sensor1);
    Serial.print(temperatur1); 
    Serial.print("Sensor 2(*C): ");
    temperatur2 = sensors.getTempC(sensor2);
    Serial.print(temperatur2); 
    Serial.print("Sensor 3(*C): ");
    temperatur3 = sensors.getTempC(sensor3);
    Serial.print(temperatur3); 
    Serial.println("DONE");
}
  if ((temperatur2 >= 20.0) && (temperatur1 < temperatur2)) {
    motor.moveTo(100);
    strcpy(bypassTxt, "offen");
    Serial.println("Move Motor in Position 100");
  }
  if ((temperatur2 >= 20.0) && (temperatur1 > temperatur2)) {
    motor.moveTo(0);
    strcpy(bypassTxt, "geschlossen");    
    Serial.println("Move Motor in Position 0");
  }
  if ((temperatur2 < 20.0) && (temperatur1 < temperatur2)) {
    motor.moveTo(0);
    strcpy(bypassTxt, "geschlossen");
    Serial.println("Move Motor in Position 0");
  }
  Serial.println(bypassTxt);
  motor.run();  
}

Die Bestimmung der Nullposition
Zur Bestimmung der Nullposition verwende ich einen Taster aus einem 3D-Drucker. Die Initialisierung nehme ich in der Setup-Routine durch:

  while ((hBval == 0) && (ButtonInit == 0)) {
     strcpy(bypassTxt, "Initialisierung");
     motor.move(-5);
     motor.run();
     hBval = digitalRead(homeButton);
     Serial.print("Homeschleife Button = ");
     Serial.println(hBval);
     motor.setCurrentPosition(0);
  }
ButtonInit = 1;

Wenn der Taster geschlossen ist, gibt der Befehl

hBval = digitalRead(homeButton);

den Wert 1 aus. Ist der Schalter offen, ist der Wert 0. Außerdem setze ich die Variable ButtonInit zu Beginn auf den Wert 0. Somit wird die While-Schleife im Setup durchlaufen, wenn der Motor sich nicht in der Ruheposition 0 befindet (Schalter geschlossen) und die Variable ButtonInit noch nicht auf 1 gesetzt wurde.
In der While-Schleife wird der Motor in kleinen Schritten motor.move(-5); langsam um jeweils 5 Steps im Uhrzeigersinn gedreht, bis der Taster geschlossen ist. Anders als motor.moveTo(100); dreht sich der Motor mit jedem Schleifendurchgang um weitere 5 Steps. Anschließend wird die Variable ButtonInit = 1 gesetzt. Die Initialisierung ist damit abgeschlossen.

Auslesen der Temperaturen in Intervallen
Von Tommy hatte ich den Hinweis auf millis() bekommen. Hier (Arduino millis anstatt von delay - Electric Junkie) wird die Verwendung auch gut beschrieben.

Eventuell würde ich noch eine Abbruchbedingung einbauen, falls die Nullposition nie erreicht werden kann.

Wenn z.B. nach 30 Sekunden die Nullposition nicht erreicht werden kann, gehst du in einen Fehlerzustand und der Webserver kann dann eine entsprechende Warnung ausgeben.

Anders hingegen wenn keine Verbindung zum Wifi aufgebaut werden kann. Da willst du vielleicht die Steuerung weiter laufen lassen. Aktuell blockiert die da auch.

1 Like
 while ((hBval == 0) && (ButtonInit == 0)) {

Warum die Abfrage nach ButtonInit == 0? Die Variable wird innerhalb der While-Schleife nicht verändert und erst nach dem Abbruch der Schleife auf 1 gesetzt.

Setup() wird nur einmal beim Start der µC aufgerufen. Demnach ist die Abfrage wirkungslos.

Wenn ButtonInit nur die Werte 0 und 1 annehmen soll, wäre es vielleicht besser die Variable als bool zu definieren und "false" bzw. "true" als Werte zu verwenden.

bool ButtinInit = false;
.
.
.
.
while (!digitalRead(homeButton)) {
     motor.move(-5);
     motor.run();
     motor.setCurrentPosition(0);
     Serial.print("Homeschleife Button = ");
     Serial.println(digitalRead(homeButton));
 }
ButtonInit = true;
1 Like

Das ist eine gute Idee. Ich denke, ich könnte einfach die Steps zählen, also die Anzahl der Schleifen, die While läuft. Und wenn der Motor 22 x die Schleife durchlaufen hat (-110 Steps) soll die While-Schleife beendet werden und eine Fehlermeldung ausgegeben werden (normal dürfte er ja maximal -100 Steps machen).

Die ButtonInit-Variable brauche ich wohl doch nicht. Die hatte ich zwischendurch eingebaut, weil ich den Eindruck hatte, dass das Setup mehrmals durchlaufen wird, daher kam diese UND-Bedingung.

Dann nehme ich das raus, wieder etwas schlanker und einfacher. :+1:

Vielen Dank,
Moritz

Hallo,
Du könntest das Referenzfahren in eine eigene Function bauen und die im loop aufrufen. ich hab da mal in meiner Bastelkiste gesucht und das gefunden.

bool revfahren() {
  stepper.setSpeed(-100);  // langsam  minus fahren
  stepper.move(2000);      // zuück fahren

  if (rev_Pos) {          // wenn schalter angefahren
    stepper.stop();

    stepper.setSpeed(300);   // normale Geschw.
    stepper.setCurrentPosition(0); // auf pos o setzten
    
    return 1;
  }
  return 0;
}

und im Loop

 rev_Pos = !digitalRead(butonRev); // Schalter einlesen

 if (rev_OK == false) {  // Rev noch nicht angefahren
    rev_OK =  revfahren();
  }

 // --- auf Pos 1 fahren
  if ( rev_OK && startbtn) {    // Start gedückt
    stepper.moveTo(setpos1);    // auf Pos 1  fahren
  }

 stepper.run();   

wenn Du magst kannst Du während des Betriebes die Variable rev_OK auf false setzen dann wird erneut Refernzpunkt angefahren ohne das der Ablauf blokiert.

Heinz

1 Like

Hallo Heinz,
danke, das gefällt mir gut. Dann könnte ich einen externen Taster oder auch in der Webseite einen Button anlegen, mit dem ich die Referenzfahrt auslösen könnte, ohne den ESP32 neu starten zu müssen.

Das werde ich einbauen, sollte ich hinbekommen😉

Gruß
Moritz

In der Praxis kann die WiFi-Verbindung verloren gehen, daher kann eine automatische Wiederverbindung sinnvoll sein. Die Funktion routerVerbindung() rufe ich in setupundloop` auf:

void routerVerbindung()
{
  unsigned long connDauer = 20000;
  unsigned long connTimer = millis();

  if (WiFi.status() != WL_CONNECTED)
  {
    DEBUG_F("Verbindung zum Router ");
    WiFi.mode(WIFI_STA);
    WiFi.disconnect();
    WiFi.begin(STA_SSID, STA_PASSWORD);
    while (WiFi.status() != WL_CONNECTED && (millis() - connTimer < connDauer)) {
      digitalWrite(LED_BUILTIN, 1);
      delay(250);
      digitalWrite(LED_BUILTIN, 0);
      delay(250);
      DEBUG_F(".");
    }
    if (WiFi.status() == WL_CONNECTED)
    {
      DEBUG_L("\nVerbunden mit: " + WiFi.SSID());
      DEBUG_L("Esp32 IP: " + WiFi.localIP().toString() + "\n");
    } else {
      DEBUG_L(" nicht hergestellt");
    }
  }
}

Ob 20 Sekunden in einer Schleife, eine blinkende LED und die Debug-Ausgaben für Dein Projekt sinnvoll sind, wäre zu hinterfragen. Soll nur eine Anregung sein :slightly_smiling_face:

1 Like

Super Ideen, die hier sprudeln und gleich mit Beispielcode! Danke, auch das werde ich in Betracht ziehen.

Ein LED-Signal braucht es in der Tat nicht, da ich den Controller nicht sehen werde, der wird ja auf dem Dachboden sein Unwesen treiben. Aber ich kann die Ausgabe ja auf die Webseite leiten (sofern die WiFi-Verbindung steht😉).

Gruß
Moritz

Hallo,
@agmue kläre den T0 und die Nachwelt bitte noch darüber auf was DEBUG_L und DEBUG_N ist. :wink:

Gruß Heinz

Gerne!

Da der ESP32 nicht unbedingt am seriellen Monitor hängt, kann man die Ausgabe wahlweise verwenden oder auch nicht:

//#define DEBUGGING                     // Einkommentieren für die Serielle Ausgabe

#ifdef DEBUGGING
#define DEBUG_B(...) Serial.begin(__VA_ARGS__)
#define DEBUG_P(...) Serial.print(__VA_ARGS__)
#define DEBUG_L(...) Serial.println(__VA_ARGS__)
#define DEBUG_F(...) Serial.printf(__VA_ARGS__)
#else
#define DEBUG_B(...)
#define DEBUG_P(...)
#define DEBUG_L(...)
#define DEBUG_F(...)
#endif

Sehr hilfreich ist auch Debug mit Telnet: Debug mit Telnet ESP8266 (und ESP32 ungetestet)), inzwischen von mir auch für den ESP32 getestet und für sehr gut befunden.

1 Like

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