Illustration des Filamentextruders

DIY Filamentextruder – Teil 1: Hardware

DIY Filamentextruder – Teil 1: Hardware

Ich versuche in dieser Serie einen eigenen Filamenextruder zu bauen. Es gibt ja schon einige tolle Projekte auf Instructables und dergleichen. Den besten den ich bisher finden konnte, hab ich auf Thingiverse entdeckt. Es geht um den Lyman Extruder V5 mit Ramps-Steuerung und der Firmware von Mulier.

Doch das Projekt hat für mich eine Problem: Den Filamentdurchmesser durch Differenz Geschwindigkeit einzustellen, halte ich für eine sehr gute Idee, jedoch ist der Sensor für die meisten Maker suboptimal. Daher versuche ich das ganze über einen gehackten digitalen Messschieber umzusetzen. Die bekommt man leicht im Netz und sind nicht schwer zu modifizieren.

Außerdem versuche ich eine Firmware auf ESP32 Basis zu schreiben, damit man, ähnlich wie mit Octoprint, direkt vom Rechner, Handy oder Tablet steuern kann.

Letztendlich muss der Filamentextruder soweit automatisiert werden, dass kein Eingreifen erforderlich ist. Man drückt auf Start und die Logik soll den Prozess steuern. Ich werde nach und nach weitere Teile ergänzen.

Teil 1: Förderschnecke

Teileliste

  • 1x 18mm Spiralbohrer
  • 1x 1/2″ Rohr (gleiche Länge etwa wie der Bohrer)
  • 2x 1/2″ Bodenflansch
  • 1x 1/2″ Gewindekappe
  • 4x M10 x 150 Maschinenschrauben
  • 4x M10 Muttern (zb. Selbstsichernd)
  • 1x Thermalbarriere aus Holz
  • 1x Axialkugellager
  • 1x 12V DC Getriebemotor
  • 1x L298N Motortreiber
  • 1x 3D-Druck Kupplung
  • 1x Heizband 230V / 25×25 mm
  • 1x SSR 25A / 3-32V DC / 24-380V AC
  • 1x Max6675 SPI Wandler
  • 1x K-Type Thermistor WRNT01
  • 1x Schaltnetzteil 12V / 30A

Schnecke

Als Förderschnecke nehme ich einen 18mm Spiralbohrer der sich in einem Halb Zoll Rohr (Außendurchmesser 22mm) dreht. Das Rohr musste ich an den Enden etwas mit dem Dremel abschleifen, da hier noch Lunker und Nähte waren. Das Rohr wird einfach in einen Flansch geschraubt.

18mm Spiralbohrer

Thermalbarriere

Dahinter kommt eine Thermalbarriere aus Holz. Hier habe ich ein paar gleiche Scheiben aus Holz mit dem K40 gelasert. Natürlich geht hier genauso gut ein Holzblock, in den ein 18mm Loch für die Schnecke, sowie vier M10 Durchgangslöcher für die Schrauben gebohrt werden müssen. Hier muss man darauf achten, dass die Löcher zum Flansch passen.

Antrieb

Als Antrieb nutze ich einen 12V DC Getriebemotor mit etwa 32 U/min bei 12V. Ich nutze diesen <hier/>. Ob er stark genug ist muss ich noch testen. Außerdem nutze ich ein L298N Treiber um den Motor mit dem ESP32 zu steuern. Hier ist der <Link/> zum Produkt. Der Motor wird mit einem 3D-Druck Adapter an den Bohrer gekoppelt.

Kupplung

Als Kupplung habe ich ein 3D-gedrucktes Adapterstück genommen. Je nach Motor und Bohrer müsst ihr hier ggf. ein eigenes Konstruieren. Alternativ geht sicherlich auch eine flexible Wellenkupplung, wie man sie für 3D-Drucker kennt. Meinen Adapter habe ich mit Madenschrauben auf die beiden Wellen-Enden geklemmt.

Lagerung

Wichtig ist außerdem ein Axialkugellager, welches die Rückstellkraft der Förderschnecke aufnehmen kann. Dieses wird ebenfalls durch einen Flansch mechanisch mit dem Förderrohr gekoppelt. Die Holzplatten dienen nur der Halterung. Diese sind ebenfalls mit dem K40 hergestellt. Zum Lasern empfiehlt sich ggf. ein lokales FabLab.

Heizelement

Als Heizelement nutze ich ein 230V Heizband mit 25mm Durchmesser, wie dieses <hier/>. Über ein Solid State Relais schalte ich den Strom. Hier nutze ich dieses <hier/> von Reichelt. Wichtig ist, dass es induktive Lasten verträgt. Zuvor hatte ich eines von Amazon, welches nicht funktionierte. Das liegt an der Funktionsweise von SSRs. Es gibt verschiedene Nutzungstypen, sogenannte Gebrauchskategorien. Diese muss zur Art des Relais passen. Für die unterschiedlichen Kategorien sind angaben zur maximalen Spannung und Stromstärke auf dem Relais angegeben.

Unsere Heizmanschette hat 360W, das ergibt ca. 1,5A bei 230V, also sollte die maximale Last von 5A nicht erreicht werden.

Temperaturmessung

Für die Temperaturmessung nutze ich einen NTC – Thermistor mit Spannungsteiler. Dazu nutze ich einen 10k Ohm Referenzwiderstand. Mein Temperaturfühler kann 0 bis 600°C messen, das ist für unsere Zwecke mehr als genug. Der Schaltplan sieht wie folgt aus:

Alternativ funktioniert ein Set aus Temperaturfühler (K-Typ Thermoelement) und Wandler Platine wie dieses <hier/>. So erhalten wir ein SPI Signal für den ESP32. Das gute am MAX6675 (der Wandler) ist, dass sie die Platinen Temperatur mit einbezieht und dadurch den korrekten Temperaturwert ausgibt.

Nozzle

Für die Nozzle nutze ich eine 1/2 Zoll Rohrkappe in die ich ein 4mm Loch gebohrt habe. Zwischen Rohr und Kappe ist ein 1/2″ Fitting aus Rotguss. Auf diesem sitzt das Heizelement und der Temperaturfühler. Letzterer wird zuerst mit CaptonTape aufgeklebt, danach kommt das Heizelement darüber.

Netzteil / Spannungsversorgung

Da ich 230V, 12V und 5V brauche, habe ich mir ein Schaltnetzteil besorgt. Hier nutze ich dieses <hier/>. Dort greife ich direkt am Eingang die 230V für das Heizelement ab, 12V für den DC Motor und zusätzlich habe einen DC-Stepdown Converter von 12V für die 5V des ESP32. Der kommt dann einfach per USB Kabel ran.

Hopper

Natürlich fehlt noch der Hopper. Hier nutze ich auch wieder den K40 um die Teile zu produzieren. Ich denke die Dateien vom Lyman Extruder sollten für euch aber auch gut funktionieren, solltet ihr keinen Laser zur Verfügung haben. Ich habe 3mm Pappelholz genommen, weil ich das noch hatte. Nehmt aber lieber eher 4 oder sogar 6mm dickes Holz, das wird deutlich stabiler!

Die Platten werden dann einfach zusammengesetzt und verleimt. Ich habe den Aufbau auf eine Grundplatte aufgebschraubt.

Testing

Um die Geschwindigkeit des Motors zu steuern müssen wir ein PWM Signal an den Enable Pin auf dem L298N senden. Die Richtung wird über Pin A High / Pin B Low in die eine, bzw. umgekehrt in die andere Richtung bestimmt. Wenn A und B gleichzeitig High oder Low geschaltet werden, dreht der Motor sich nicht. Der L298N unterstützt zwei Motoren. Den zweiten Ausgang werden wir für die Spulenaufwicklung nutzen. Ich werde versuchen auf eine zusätzliche Zugeinheit zu verzichten.

Hier ist noch der Schaltplan:

Je nachdem wie ihr den DC-Motor an OUT1 bzw. OUT2 angeklemmt habt, ist entweder GPIO26 oder GPIO27 für die Vorwärts-Richtung zuständig. Dreht der Motor in die falsche Richtung (gut zu erkennen an der Schnecke), tauschen wir einfach OUT1 und OUT2.

PWM

Für das PWM Signal müssen wir uns beim ESP32 mit ledc Befehlen behelfen, denn AnalogWrite ist nicht implementiert. Dafür sind die PWM – Pins aber frei wählbar.

Damit wir alles komfortabel Steuern können, nutzen wir einen Webserver. Wer noch nie etwas mit Webservern auf ESP gemacht hat, sollte sich den tollen Guide von TTAPA auf GitHub ansehen.

// Load Wi-Fi library
#include <WiFi.h>

// Replace with your network credentials
const char* ssid = "DEINWIFI";
const char* password = "DEINPW";

// Set web server port number to 80 (basic)
WiFiServer server(80);

// Variable to store the HTTP request
String header;

// Auxiliar variables to store the current output state
String augerMotorState = "off";

// Decode HTTP GET value for Auger Speed Slider
String valueString = String(5);
int pos1 = 0;
int pos2 = 0;

//---- AugerMotor ---
const int augerMotorPin1 = 27; //for Direction - 27 on && 26 off forward
const int augerMotorPin2 = 26;
const int augerEnablePin = 14; //for Speed

// Setting PWM properties
const int freq = 30000;
const int pwmChannel = 0;
const int resolution = 8;
int dutyCycle = 250;

// ----- Timer -----
// Current time
unsigned long currentTime = millis();
// Previous time
unsigned long previousTime = 0;
// Define timeout time in milliseconds (example: 2000ms = 2s)
const long timeoutTime = 2000;

//----- AUGER SETUP -----
void setupAuger() {
  Serial.println("Function: setupAuger...");

  pinMode(augerMotorPin1, OUTPUT);
  pinMode(augerMotorPin2, OUTPUT);
  pinMode(augerEnablePin, OUTPUT);
  Serial.println("Task: pinModes set...");

  // configure LED PWM functionalitites
  ledcSetup(pwmChannel, freq, resolution);
  Serial.println("Task: pwm set...");

  // attach the channel to the GPIO to be controlled
  ledcAttachPin(augerEnablePin, pwmChannel);
  Serial.println("Task: pwm channel attached...");

  // Set outputs to LOW to ensure Stop Motor
  digitalWrite(augerMotorPin1, LOW);
  digitalWrite(augerMotorPin2, LOW);
  Serial.println("Task: setting motor directions to low...");

  Serial.println("Function: setupAuger... successfull");
}

//----- WIFI SETUP -----
void setupWifi() {
  Serial.println("Function: setupWifi...");

  // Connect to Wi-Fi network with SSID and password
  Serial.print("Connecting to: ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  // Print local IP address and start web server
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  server.begin();

  Serial.println("Function: setupWifi... successfull");
}

//----- ARDUINO SETUP -----
void setup() {
  Serial.begin(115200);
  Serial.println("ESP32 Extruder Control - Welcome to Debugmode");
  setupAuger();
  setupWifi();

}

//----- ARDUINO LOOP -----
void loop() {
  WiFiClient client = server.available();   // Listen for incoming clients

  if (client) {                             // If a new client connects,
    currentTime = millis();
    previousTime = currentTime;

    Serial.println("New Client.");          // print a message out in the serial port
    String currentLine = "";                // make a String to hold incoming data from the client

    while (client.connected() && currentTime - previousTime <= timeoutTime) {  // loop while the client's connected
      currentTime = millis();
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        header += c;

        if (c == '\n') {                    // if the byte is a newline character
          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();

            // turns the Auger Motor on and off
            if (header.indexOf("GET /augerMotorForward/on") >= 0) {
              Serial.println("Auger Motor on - Forward");
              augerMotorState = "forward";
              digitalWrite(augerMotorPin1, HIGH);
              digitalWrite(augerMotorPin2, LOW);
              ledcWrite(pwmChannel, dutyCycle);

            } else if (header.indexOf("GET /augerMotorForward/off") >= 0) {
              Serial.println("Auger Motor off");
              augerMotorState = "off";
              digitalWrite(augerMotorPin1, LOW);
              digitalWrite(augerMotorPin2, LOW);

            } else if (header.indexOf("GET /augerMotorBackward/on") >= 0) {
              Serial.println("Auger Motor on - Backward");
              augerMotorState = "Backward";
              digitalWrite(augerMotorPin2, HIGH);
              digitalWrite(augerMotorPin1, LOW);
              ledcWrite(pwmChannel, dutyCycle);

            } else if (header.indexOf("GET /augerMotorBackward/off") >= 0) {
              Serial.println("Auger Motor off");
              augerMotorState = "off";
              digitalWrite(augerMotorPin2, LOW);
              digitalWrite(augerMotorPin1, LOW);
            }

            // Display the HTML web page
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");

            // CSS to style the on/off buttons
            // Feel free to change the background-color and font-size attributes to fit your preferences
            client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");
            client.println(".button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px;");
            client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");
            client.println(".button2 {background-color: #555555;}</style></head>");

            //CSS for the Slider
            client.println("<style>body { text-align: center; font-family: \"Trebuchet MS\", Arial; margin-left:auto; margin-right:auto;}");
            client.println(".slider { width: 300px; }</style>");
            client.println("<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js\"></script>");


            // Web Page Heading
            client.println("<body><h1>ESP32 Extruder Control (WIP)</h1>");

            // Display current state, and ON/OFF buttons for Forward
            client.println("<p>Auger Motor - State " + augerMotorState + "</p>");
            // If the State is off, it displays the ON button
            if (augerMotorState == "off") {
              client.println("<p><a href=\"/augerMotorForward/on\"><button class=\"button\">Forward</button></a></p>");
            } else {
              client.println("<p><a href=\"/augerMotorForward/off\"><button class=\"button button2\">OFF</button></a></p>");
            }

            // Display current state, and ON/OFF buttons for Backward
            client.println("<p>Auger Motor - State " + augerMotorState + "</p>");
            // If the State is off, it displays the ON button
            if (augerMotorState == "off") {
              client.println("<p><a href=\"/augerMotorBackward/on\"><button class=\"button\">Backward</button></a></p>");
            } else {
              client.println("<p><a href=\"/augerMotorBackward/off\"><button class=\"button button2\">OFF</button></a></p>");
            }

            //Display a Speedslider for Auger Motor
            client.println("<p>Current DutyCycle: <span id=\"augerSpeedSliderPos\"></span></p>");
            client.println("<input type=\"range\" min=\"0\" max=\"255\" class=\"slider\" id=\"augerSpeedSlider\" onchange=\"augerSpeed(this.value)\" value=\"" + valueString + "\"/>");
            client.println("<script>var slider = document.getElementById(\"augerSpeedSlider\");");
            client.println("var augerSpeedSliderP = document.getElementById(\"augerSpeedSliderPos\"); augerSpeedSliderP.innerHTML = slider.value;");
            client.println("slider.oninput = function() { slider.value = this.value; augerSpeedSliderP.innerHTML = this.value; }");
            client.println("$.ajaxSetup({timeout:1000}); function augerSpeed(pos) { ");
            client.println("$.get(\"/?value=\" + pos + \"&\"); {Connection: close};}</script>");

            //GET /?value=180& HTTP/1.1
            if (header.indexOf("GET /?value=") >= 0) {
              pos1 = header.indexOf('=');
              pos2 = header.indexOf('&');
              valueString = header.substring(pos1 + 1, pos2);

              //Set Speed
              dutyCycle = valueString.toInt();
              ledcWrite(pwmChannel, dutyCycle);
              Serial.println(dutyCycle);
            }

            //body
            client.println("</body></html>");

            // The HTTP response ends with another blank line
            client.println();
            // Break out of the while loop
            break;
          } else { // if you got a newline, then clear currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }
      }
    }
    // Clear the header variable
    header = "";
    // Close the connection
    client.stop();
    Serial.println("Client disconnected.");
    Serial.println("");

    Serial.println(digitalRead(augerMotorPin1));
    Serial.println(digitalRead(augerMotorPin2));
    Serial.println(digitalRead(augerEnablePin));
  }
}

Step by Step

Includes

#include <WiFi.h>

Hier brauchen wir nur die Wifi.h Bibliothek, die für uns die Arbeit mit dem WLAN übernimmt.

Log-In Daten

// Replace with your network credentials
const char* ssid = "DEINWIFI";
const char* password = "DEINPW";

Natürlich brauchen wir Log-In Daten für das Wlan. Diese Speichern wir in zwei Variablen vom Typ const char*.

Webserver

WiFiServer server(80);

Hier starten wir den Webserver mit dem Namen server auf Port 80. Hier solltet ihr immer Port 80 wählen. Das ist der Standard-Port auf dem die meisten Websiten ausgeliefert werden. So weiß der Browser wo er hingucken muss.

HTTP Request

// Variable to store the HTTP request
String header;

// Auxiliar variables to store the current output state
String augerMotorState = "off";

// Decode HTTP GET value for Auger Speed Slider
String valueString = String(5);
int pos1 = 0;
int pos2 = 0;

Die Website wird im Browser geladen und das Endgerät mit dem ihr diese Seite anschaut wird zum Client. Der Arduino-Sketch auf dem ESP32 ist der Server. Server und Client kommunizieren miteinander. Dazu kann man verschiedene Protokolle nutzen. Hier nutzen wir für den Anfang einen HTTP GET Request. Dieser wird im String Header gespeichert. Außerdem steuern wir die Geschwindigkeit mit einem Slider. Wir brauchen außerdem den aktuellen Zustand des Motors. Hier wäre eine Variable vom Typ bool ebenfalls geeignet.

Motor

//---- AugerMotor ---
const int augerMotorPin1 = 27; //for Direction - 27 on && 26 off forward
const int augerMotorPin2 = 26;
const int augerEnablePin = 14; //for Speed

// Setting PWM properties
const int freq = 30000;
const int pwmChannel = 0;
const int resolution = 8;
int dutyCycle = 250;

Wir legen die Pins für den Motor in Variablen vom Typ const int ab. Da wir diese Werte ohnehin nicht verändern. Für die Berechnung der PWM Werte mit ledc brauchen wir außerdem eine Frequenz, einen Kanal, die Auflösung und einen dutyCycle. Mehr dazu gibt es in der ESP-Dokumentation.

Timer

// ----- Timer -----
// Current time
unsigned long currentTime = millis();
// Previous time
unsigned long previousTime = 0;
// Define timeout time in milliseconds (example: 2000ms = 2s)
const long timeoutTime = 2000;

Dann brauchen wir auch noch ein paar Timer, damit wir Pseudo-Multitasking umsetzen können. Wenn wir Delay nutzen würden, könnten wir in der Zwischenzeit keinen anderen Code ausführen.

Funktion Auger Setup

//----- AUGER SETUP -----
void setupAuger() {
  Serial.println("Function: setupAuger...");

  pinMode(augerMotorPin1, OUTPUT);
  pinMode(augerMotorPin2, OUTPUT);
  pinMode(augerEnablePin, OUTPUT);
  Serial.println("Task: pinModes set...");

  // configure LED PWM functionalitites
  ledcSetup(pwmChannel, freq, resolution);
  Serial.println("Task: pwm set...");

  // attach the channel to the GPIO to be controlled
  ledcAttachPin(augerEnablePin, pwmChannel);
  Serial.println("Task: pwm channel attached...");

  // Set outputs to LOW to ensure Stop Motor
  digitalWrite(augerMotorPin1, LOW);
  digitalWrite(augerMotorPin2, LOW);
  Serial.println("Task: setting motor directions to low...");

  Serial.println("Function: setupAuger... successfull");
}

Ich nutze gerne Unterfunktionen, auch schon im Setup, damit der Code schön Übersichtlich bleibt. Wir rufen diese Funktion später in der Arduino Setup Routine auf.

Zuerst legen wir mit pinMode die Modi der Pins fest, damit die Signale auch tatsächlich stimmen.

Dann kommt die LEDC-Konfiguration mit den eben erwähnte Variablen. Hier müssen wir auch einen Pin zuweisen.

Zum Schluss stellen wir sicher, dass der Motor steht, in dem wir beide Pins für die Richtung auf Low setzen.

Logik-Level

Jeder Microcontroller hat ein Logik-Level. Die meisten Arduino-Boards nutzen ein 5V Level, der ESP32 nutzt 3,3V. Ein Low Signal bedeutet, die Spannung ist in der Nähe von 0V, ein High Signal bedeutet, die Spannung ist in der Nähe von 3,3V. Es gibt eine bestimmte Grenze, ab der das Signal als eines von beiden gewertet wird. Diese findet man immer in der Doku des Boards.

Wifi Setup

//----- WIFI SETUP -----
void setupWifi() {
  Serial.println("Function: setupWifi...");

  // Connect to Wi-Fi network with SSID and password
  Serial.print("Connecting to: ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  // Print local IP address and start web server
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  server.begin();

  Serial.println("Function: setupWifi... successfull");
}

Diese Funktion wird ebenfalls in der Arduino-Setup aufgerufen und kümmert sich um die korrekte Zuordnung aller fürs Wlan relevanten Dinge. Wenn wir erfolgreich Verbunden sind, wird die IP des ESP32 auf dem Seriellen Monitor ausgegeben.

Arduino Setup

//----- ARDUINO SETUP -----
void setup() {
  Serial.begin(115200);
  Serial.println("ESP32 Extruder Control - Welcome to Debugmode");
  setupAuger();
  setupWifi();

}

Diese setup gehört zur Arduino-Standard funktion und wird als erste Routine nach den globalen Variablen gestartet. Wir starten von hier aus unsere eigenen Setup funktionen. Außerdem öffnen wir mit Serial.begin() den Seriellen Monitor. 115200 ist die sogenannte Baudrate, eine Datenrate, mit der PC und Arduino kommunizieren.

Arduino Loop

//----- ARDUINO LOOP -----
void loop() {
  WiFiClient client = server.available();   // Listen for incoming clients

  if (client) {                             // If a new client connects,
    currentTime = millis();
    previousTime = currentTime;

    Serial.println("New Client.");          // print a message out in the serial port
    String currentLine = "";                // make a String to hold incoming data from the client

    while (client.connected() && currentTime - previousTime <= timeoutTime) {  // loop while the client's connected
      currentTime = millis();
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        header += c;

        if (c == '\n') {                    // if the byte is a newline character
          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();

Die Arduino Loop wird immer nach der Setup durchgeführt und läuft als Endlosschleife. Wenn wir am Ende angelangt sind, wird die Schleife einfach wiederholt. Die Setup dagegen wird nur ein mal ausgeführt.

WiFiClient client = server.available();   // Listen for incoming clients

Hier hören wir auf eingehende Verbindungen. Wenn der Server erreichbar ist, wird ein True Wert in die Variable client gespeichert.

if (client) {                             // If a new client connects,
    currentTime = millis();
    previousTime = currentTime;

Wenn also client = true ist, messen wir die aktuelle Laufzeit und speichern diese in die Variable currentTime. Außerdem übergeben wir diese Zeit in unsere Speichervariable previousTime.

Serial.println("New Client.");          // print a message out in the serial port
    String currentLine = "";                // make a String to hold incoming data from the client

Weiter erzeugen wir einen lokalen String um eingehende Daten zu speichern. Diese brauchen wir nicht global, also deklarieren wir die Variable nur lokal. Wir sind noch immer in der if Funktion.

while (client.connected() && currentTime - previousTime <= timeoutTime) {  // loop while the client's connected
      currentTime = millis();
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        header += c;

Während ein Client, also ein Endgerät wie euer Laptop, verbunden ist, speichern wir wieder die aktuelle Zeit und prüfen ob Daten empfangen werden. Diese Speichern wir als einzelne Buchstaben in char c und fassen diese dann in der Variable Header zusammen. Zusätzlich prüfen wir ob wir noch innerhalb der Timeout Zeit sind.

if (c == '\n') {                    // if the byte is a newline character
          // if the current line is blank, you got two newline characters in a row.

Wenn wir das Zeichen für einen Zeilenumbruch erkennen, ist der Befehl zuende. Dann können wir eine Antwort senden.

// that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();

Hier müssen wir uns an die Struktur für den HTTP Response halten, sonst versteht der Client uns nicht.

Logik

// turns the Auger Motor on and off
            if (header.indexOf("GET /augerMotorForward/on") >= 0) {
              Serial.println("Auger Motor on - Forward");
              augerMotorState = "forward";
              digitalWrite(augerMotorPin1, HIGH);
              digitalWrite(augerMotorPin2, LOW);
              ledcWrite(pwmChannel, dutyCycle);

            } else if (header.indexOf("GET /augerMotorForward/off") >= 0) {
              Serial.println("Auger Motor off");
              augerMotorState = "off";
              digitalWrite(augerMotorPin1, LOW);
              digitalWrite(augerMotorPin2, LOW);

            } else if (header.indexOf("GET /augerMotorBackward/on") >= 0) {
              Serial.println("Auger Motor on - Backward");
              augerMotorState = "Backward";
              digitalWrite(augerMotorPin2, HIGH);
              digitalWrite(augerMotorPin1, LOW);
              ledcWrite(pwmChannel, dutyCycle);

            } else if (header.indexOf("GET /augerMotorBackward/off") >= 0) {
              Serial.println("Auger Motor off");
              augerMotorState = "off";
              digitalWrite(augerMotorPin2, LOW);
              digitalWrite(augerMotorPin1, LOW);
            }

Als nächstes müssen wir die Daten des GET Requests auswerten. Wenn wir eine bestimmte URI abgefragt haben, lösen wir eine entsprechende Funktion aus. Wenn wir also auf der dargestellten Website einen Button klicken, wird eine Nachricht an den Server (den ESP) mit dem Befehl des Buttons gesendet. Dieser Antwortet mit Ok, interpretiert den Befehl und führt ihn aus, sofern er den Befehl kennt.

Website

            // Display the HTML web page
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");

            // CSS to style the on/off buttons
            // Feel free to change the background-color and font-size attributes to fit your preferences
            client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");
            client.println(".button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px;");
            client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");
            client.println(".button2 {background-color: #555555;}</style></head>");

            //CSS for the Slider
            client.println("<style>body { text-align: center; font-family: \"Trebuchet MS\", Arial; margin-left:auto; margin-right:auto;}");
            client.println(".slider { width: 300px; }</style>");
            client.println("<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js\"></script>");


            // Web Page Heading
            client.println("<body><h1>ESP32 Extruder Control (WIP)</h1>");

            // Display current state, and ON/OFF buttons for Forward
            client.println("<p>Auger Motor - State " + augerMotorState + "</p>");
            // If the State is off, it displays the ON button
            if (augerMotorState == "off") {
              client.println("<p><a href=\"/augerMotorForward/on\"><button class=\"button\">Forward</button></a></p>");
            } else {
              client.println("<p><a href=\"/augerMotorForward/off\"><button class=\"button button2\">OFF</button></a></p>");
            }

            // Display current state, and ON/OFF buttons for Backward
            client.println("<p>Auger Motor - State " + augerMotorState + "</p>");
            // If the State is off, it displays the ON button
            if (augerMotorState == "off") {
              client.println("<p><a href=\"/augerMotorBackward/on\"><button class=\"button\">Backward</button></a></p>");
            } else {
              client.println("<p><a href=\"/augerMotorBackward/off\"><button class=\"button button2\">OFF</button></a></p>");
            }

//Display a Speedslider for Auger Motor
            client.println("<p>Current DutyCycle: <span id=\"augerSpeedSliderPos\"></span></p>");
            client.println("<input type=\"range\" min=\"0\" max=\"255\" class=\"slider\" id=\"augerSpeedSlider\" onchange=\"augerSpeed(this.value)\" value=\"" + valueString + "\"/>");
            client.println("<script>var slider = document.getElementById(\"augerSpeedSlider\");");
            client.println("var augerSpeedSliderP = document.getElementById(\"augerSpeedSliderPos\"); augerSpeedSliderP.innerHTML = slider.value;");
            client.println("slider.oninput = function() { slider.value = this.value; augerSpeedSliderP.innerHTML = this.value; }");
            client.println("$.ajaxSetup({timeout:1000}); function augerSpeed(pos) { ");
            client.println("$.get(\"/?value=\" + pos + \"&\"); {Connection: close};}</script>");

            //GET /?value=180& HTTP/1.1
            if (header.indexOf("GET /?value=") >= 0) {
              pos1 = header.indexOf('=');
              pos2 = header.indexOf('&');
              valueString = header.substring(pos1 + 1, pos2);

              //Set Speed
              dutyCycle = valueString.toInt();
              ledcWrite(pwmChannel, dutyCycle);
              Serial.println(dutyCycle);
            }

            //body
            client.println("</body></html>");

            // The HTTP response ends with another blank line
            client.println();
            // Break out of the while loop
            break;
          } else { // if you got a newline, then clear currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }
      }
    }
    // Clear the header variable
    header = "";
    // Close the connection
    client.stop();
    Serial.println("Client disconnected.");
    Serial.println("");

Der nächste Abschnitt ist einfach eine unelegante Art eine Website darzustellen. Wir basteln uns mit dem client.print Befehl den gesamten HTML Code zusammen. In Teil 3 lösen wir das schöner. Am Ende dieses Codes beenden wir die Client Connection, die Website bleibt bestehen, bis sie beim nächsten Request aktualisiert wird.

Fazit

Wie ihr seht, verbindet sich der ESP32 mit dem W-LAN und über den Seriellen Monitor wird die IP mitgeteilt. Diese kopieren wir in den Browser. Was wir sehen sind zwei Knöpfe für die Motorsteuerung und einen Schieberegler für die Umdrehungsgeschwindigkeit. So können wir schonmal den Antrieb testen.

Wenn ihr alle Pins korrekt verdrahtet habt, sollte der Motor in beide Richtungen angesteuert werden können. Außerdem geht der Slider von 0 bis 255. Alle Werte unter 100 lassen den Motor stehen. Die Spindel bzw. Förderschnecke unseres DIY Filamentextruders funktioniert also schonmal.

Weiter geht es mit der Temperaturregelung in Teil 2.

1 Kommentar

Schreibe einen Kommentar