Weizen Eingießanlage: Unterschied zwischen den Versionen

Aus HSHL Mechatronik
Zur Navigation springen Zur Suche springen
 
(78 dazwischenliegende Versionen von 2 Benutzern werden nicht angezeigt)
Zeile 3: Zeile 3:
<!-- Kopieren Sie diesen Header in Ihren Artikel, damit er aufgelistet wird.  -->
<!-- Kopieren Sie diesen Header in Ihren Artikel, damit er aufgelistet wird.  -->
'''Autor: Philipp Sander, Dennis Fleer''' <br/>
'''Autor: Philipp Sander, Dennis Fleer''' <br/>
'''Betreuer: noch offen''' <br/>
'''Betreuer: Prof. Dr. Mirek Göbel''' <br/>




== Einleitung ==
== Einleitung ==
Dieser Artikel beschreibt den Aufbau und die Funktion einer Weizen-Eingießanlage. Hierbei gibt es eine Vorrichtung für die Flasche und eine für ein Glas. Diese werden in einem bestimmten Winkel so verfahren, dass immer ein optimal eingegossenes Weizen entsteht. Der Winkel zwischen Flasche und Glas wird durch einen Kraftsensor geregelt, der den Füllstand des Glasses bestimmt.
Dieser Artikel beschreibt den Aufbau und die Funktion einer Weizenglas-Eingießanlage. Hierbei gibt es eine Halterung für die Flasche und eine für ein Glas. Beide sind jeweils an einer rotierenden Holzplatte montiert. Diese Holzplatte wird mittels Elektromotor rotiert. Über einem Drucksensor wird der Füllstand des Weizenglases ermittelt und eine optimale Einfüllung des Glases erreicht.
 


== Anforderungen ==
== Anforderungen ==
Zeile 21: Zeile 20:
|-
|-
| 1
| 1
| Halterungen zeichnen und fertigen
| Die Halterungen müssen an der Holzplatte befestigt werden.
| Hardware
| Hardware
| noch offen
| Dennis Fleer, Philipp Sander
|-
|-
| 2
| 2
| Mechanik konstruieren und einbauen
| Der Motor muss die Holzplatte drehen können.
| Hardware
| Hardware
| noch offen
| Dennis Fleer, Philipp Sander
|-
|-
| 3
| 3
| Elektrik einbauen und verkabeln
| Die Elektrik muss eingebaut und verkabelt werden.
| Hardware
| Hardware
| noch offen
| Dennis Fleer, Philipp Sander
|-
|-
| 4
| 4
| Regelung des Kraftsensors
| Die Regelung muss durch den Kraftsensor erfolgen.
| Software
| Software
| noch offen
| Dennis Fleer, Philipp Sander
|-
|-
| 5
| 5
| Motoren Ansteuerung mittels Regler
| Der Motor muss mittels Regler angesteuert werden.
| Software
| Software
| noch offen
| Dennis Fleer, Philipp Sander
|-
|-
|}
|}
Zeile 54: Zeile 53:
Im folgenden Systementwurf wird das Projekt in Systemkomponenten unterteilt:
Im folgenden Systementwurf wird das Projekt in Systemkomponenten unterteilt:


* '''Arduino: ''' In Matlab Simulink programmierte Steuerung.
* '''Arduino: ''' In Matlab Simulink programmierte Ansteuerung des Motors und Regelung.


* '''Motoren: ''' Steuerung der perfekten Positionen
* '''Motor: ''' Drehbewegung der Holzplattform 


* '''Drucksensor: ''' Kraftmessung für die Füllstandserkennung
* '''Drucksensor: ''' Kraftmessung für die Füllstandserkennung


* '''Regler: ''' Regler zum rausrechnen von winkelverfälschten Kräften
* '''Regler: ''' Füllstandsregelung des Weizenglases
<br/>
<br/>
<gallery widths="500" heights="500">
<gallery widths="500" heights="500">
Datei:Weizen Eingießanlage Schematisch.png | Weizen Eingießanlage Schematisch - Skizze
Datei:Weizen Eingießanlage Schematisch.png | Weizen Eingießanlage Schematisch - Skizze
Datei:Technischer Systementwurf Weizen Eingiesanlage.png | Technischer Systementwurf
Datei: RegelKreis_Weizenglas.png| Weizen Eingießanlage Regelkreis
</gallery>
 
'''Ansatz zur Herleitung der Übertragungsfunktion:'''
 
<gallery widths="500" heights="500">
Datei:ÜbertragungsfunktionHerleitungAnsatz.png | Ansatz zur Herleitung der Übertragungsfunktion
</gallery>
</gallery>
<!-- Füllen Sie Ihre Projektskizze bis hierher aus. Fügen Sie einen Projektplan unten ein.  -->
<!-- Füllen Sie Ihre Projektskizze bis hierher aus. Fügen Sie einen Projektplan unten ein.  -->


== Komponentenspezifikation ==
== Komponentenspezifikation ==
{| class="wikitable"
! style="font-weight: bold;" | Nr.
! style="font-weight: bold;" | Bauteil
! style="font-weight: bold;" | Beschreibung
|-
| 1
| Arduino MEGA 2560
|
*Versorgungsspannung: 7V-12V (empfohlen)
*Betriebsspannung: 5V
*54 digitale Ein-/Ausgänge und 16 analoge Eingänge
|-
| 2
| DM442 Motortreiber
|
* Vollschritt Einstellung
* Versorgungsspannung 24V
|-
| 3
| HX711 Wägezelle
|
* Versorgungsspannung 5V
* Messbereich: bis zu 5Kg
* 24 Bit ADC
|-
| 4.1
| Schrittmotor ...
|
* Betriebsspannung 3V
* Haltedrehmoment mind. 1,6 Nm
|}


== Umsetzung (HW/SW) ==
== Umsetzung (HW/SW) ==
=== Hardware ===
'''Projektübersicht:'''
In unserem Projekt liegen die Anforderung vorrangig darin eine motorbetriebene drehbare Holzplatte zu konstruieren und dieses System mit einer geeigneten Motoransteuerung zu betreiben. In bezug auf Kosten und Gewicht haben wir uns daher für eine Holzkonstruktion entschieden. Die Holzplatte, an welcher die Halterungen für die Bierflasche und Weizenflasche sowie der Sensor befestigt ist, wird von einer Massivholzwelle getragen. Diese Welle wird mittels Lageraufständen auf einer Holzgrundplatte befestigt. Die Welle wird durch eine Riemenübersetzung mit dem Schrittmotor angetrieben. 
'''3D Ansicht:'''
Baugruppen Model
<br/>
<gallery widths="500" heights="500">
Datei:Eingießanlage Baugruppe final.png| Eingießanlage -  SolidWorks - [[Datei:MaschineBaugrp.sldasm|mini]]
</gallery>
'''Motor:'''
Wir wollten zunächst den Motor des Arduino-Mega-Sets verwenden. Allerdings ist uns nach einer kleinen Testfahrt aufgefallen dass dieser zu wenig Haltedrehmoment aufweist. Daher haben wir auf einen ausgediehnten Motor aus unsere Partnerfirma "Diebold-Nixdorf Inc." gesetzt. Dieser Motor war nun auch in der Lage unsere Anforderungen zu erfüllen. Um diesen Motor zu betreiben wählten wir einen Motortreiber welchen wir ebenfalls in einem ausgediehnten Geldautomaten-System fanden.
'''Konstruktion:'''
Die Konstruktion der Halterung ist auf den ersten Blick sehr simpel. Dennoch galt es für uns die Kosten sowie das Gewicht niedrig zu halten. Daher belaufen sich fast alle Komponenten auf Holz und Kunststoff. Für die meisten Komponenten konnten wir auf unser Betriebsinternes Lager zurückgreifen lediglich die Holzplatte und die Halterungen für Flasche und Glas waren noch nicht vorhanden. Die Halterungen für Glas und Flasche wurden durch einen 3D-Kunststoffdruck gefertigt. Zudem mussten wir uns überlegen wie wir die Flasche in der Halterung befestigen. Um auch hier eine günstige, einfache und leichte Lösung zu erreichen, setzen wir auf die Spannkraft zweier Gummis, welche die Flasche im Halter halten.
'''Wägezelle HX711:'''
Messprinzip: Durch das zu messende Gewicht wird die Wägezelle leicht gebogen. An der Seite befinden sich 4 Dehnungsmesstreifen, welche als Wheatsstone Brücke angeordnet sind. Der Widerstand der Messstreifen ändert sich mit dem Dehnungsgrad. Somit ändert sich bei einer Last der Widerstand und damit auch der Spannungsabfall der Brückenschaltung. Der Spannungsabfall zwischen den Punkten A und B wird mit einem A/D Wandler ausgewertet.
<gallery widths="500" heights="500">
Datei:Messschaltung HX711 DMS.png | - Messchaltungsaufbau
</gallery>
   
Da die Wägezelle über die physikalische Kraft der Gewichtskraft funktioniert, war es eine Herausforderung in unserem drehenden System die korrekten Werte für den Füllstand zu erhalten. Dies war deshalb so schwierig weil sich die Wägezelle immer in einem anderen Winkel zur Gewichtskraft des Füllstandes befindet. Aus diesem Grund mussten wir die Messwerte noch einmal mit dem cos des Winkels multiplizierten um die korrekte Gewichtskraft zu erhalten. Wir verwenden außerdem einen Look-up-Table um uns die Verarbeitung der Messdaten zu vereinfachen. Konkret subtrahieren wir unseren gemessenen wert mit dem Wert aus dem Look-up-Table an der gleichen Winkelstellung. Somit Erhalten wir die korrekte Gewichtskraft des Pegelstandes des Glases. Dies ist dann dementsprechend unser IST-Wert.
'''PI-Regler:'''
Der IST-Wert wird nun mit dem SOLL-Wert von 500 (500ml/Kg) subtrahiert. Somit erhalten wir unsere Regelabweichung welche wir weiterhin mit dem PI-Regler verarbeiten.  Aufgrund dessen, dass 1-Motorschritt in 0,39 Grad am äußeren Umfang der Holzplatte entspricht, benötigen wir eine sehr schnelle Regulierung der Motorschritte, sobald Flüssgkeit in das Glas läuft. Daher benötigen wir eine möglichst schnelle Sprungantwort und somit einen großen P-Anteil in unserem Regler. Für unser System haben sich die Werte Kp=0,014 und Ki=0,00675 als praktikabel erwiesen. Die Stellgröße des Reglers wird direkt als Ansteuerung für den Motor genutzt.
'''Motoransteuerung:'''
Die Motoransteuerung erfolgt über den Motortreiber DM441 dieser braucht eine externe Versorgungsspannung von 24V. Außerdem werden zur Steuerung drei Pins angeschlossen.
- '''ENA:''' Sorgt für das Starten des Motors und zusätzlich auch für einen Haltestrom.
- '''DIR:''' Gibt die Richtung, in die der Motor fahren soll.
- '''PUL:''' Über diesen Pin wird ein PWM-Signal gesendet, welches das Drehmoment und die Geschwindigkeit bestimmt.
'''Verdrahtungsplan:'''
<gallery widths="500" heights="500">
Datei:Verdrahtungsplan Eingießanlage.png| Verdrahtungsplan Eingießanlage - [[Datei:Verdrahtungsplan Hopfomat.zip|mini]]
</gallery>
=== Software ===
'''Programmablaufplan:'''
[[Datei:Berechnung Regelabweichung.png|mini]]
<gallery widths="500" heights="500">
Datei:Hopfomat PAP.png| Programmablaufplan - [[Datei:HopfomatPAP.zip|mini]]
</gallery>
'''Simulink Regler Model:'''
<gallery widths="500" heights="500">
[[Datei:Simulink-Regler-Final.png| Weizenglas Eingießanlage - Regler - Simulink - [[Datei:Simulink Regler.zip|mini]]]]
</gallery>
{| class="mw-datatable"
! style="font-weight: bold;" |
! style="font-weight: bold;" |
|+ style = "text-align: left"|
|-
|[[Datei:Berechnung Regelabweichung.png|300px|mini|links|Berechnung der Regelabweichung Subsystem]]
|[[Datei:PI-Regler.png|300px|mini|rechts|PID-Regler Subsystem]]
|}
'''DM442.h:'''
<div style="width:1100px; height:500px; overflow:scroll; border: hidden">
<syntaxhighlight lang="cpp" style="border: none; background-color: #EFF1C1; font-size:larger">
#ifndef DM442_h
#define DM442_h
#define RIGHT (bool) true
#define LEFT (bool) false
//Motor Driver Leadshine DM442
class DM442
{
public:
void begin(int dir, int ena, int pul);
void start();
void stop();
//Use the defines for setting the direction
void setDirection(bool direction); //sets the rotating direction
void setFrequency(int stepsPerSecond); //sets the speed
void setDutyCycle(int percent); //sets the torque
void resetSteps(); //Reset of counted steps
void singleStep(); //Drive one step
void driveSteps(int steps); //Drive many steps
long getSteps(); //Get driven steps
private:
int dir; //Direction-Pin address
int ena; //Enable-Pin address
int pul; //Pulse-Pin address
int frequency; //highs per second
int dutyCycle; //in %
bool running; //Motor can drive
bool direction; //motor rotates in direction TRUE = right | FALSE = left viewed from the front
long steps; //positive value for right steps | negative for left steps
};
#endif
</syntaxhighlight>
</div>
'''DM442.cpp:'''
<div style="width:1100px; height:500px; overflow:scroll; border: hidden">
<syntaxhighlight lang="cpp" style="border: none; background-color: #EFF1C1; font-size:larger">
#include "DM442.h"
#include "Arduino.h"
void DM442::begin(int dir, int ena, int pul) {
this->dir = dir;
this->ena = ena;
this->pul = pul;
pinMode(dir, OUTPUT);
pinMode(ena, OUTPUT);
pinMode(pul, OUTPUT);
this->steps = 0;
this->running = false;
this->direction = RIGHT;
this->dutyCycle = 50;
this->frequency = 1;
TCCR4B = TCCR4B & 0b11111000 | 0x01;
}
void DM442::start() {
digitalWrite(this->ena, LOW);
}
void DM442::stop() {
digitalWrite(this->ena, HIGH);
}
void DM442::setDirection(bool direction) {
this->direction = direction;
if (direction) digitalWrite(this->dir, HIGH);
else digitalWrite(this->dir, LOW);
}
void DM442::setFrequency(int stepsPerSecond) {
this->frequency = stepsPerSecond;
}
void DM442::setDutyCycle(int percent) {
this->dutyCycle = percent;
}
void DM442::singleStep() {
//analogWrite(this->pul, 255 * this->dutyCycle / 100);
if (this->frequency == 0) return;
long delayHigh = (float) this->dutyCycle / 100 * 500 / this->frequency;
long delayLow = (float) (100 - this->dutyCycle) / 100 * 500 / this->frequency;
digitalWrite(this->pul, HIGH);
delay(delayHigh);
digitalWrite(this->pul, LOW);
delay(delayLow);
this->steps++;
}
void DM442::driveSteps(int steps) {
int percentPerStep = 40 / steps;
for (int i = 0; i < steps; i++) {
setDutyCycle(10 + i * percentPerStep);
singleStep();
}
}
long DM442::getSteps() {
return this->steps;
}
void DM442::resetSteps() {
this->steps = 0;
}
</syntaxhighlight>
</div>
'''read_HX711 S-Function:'''
<div style="width:1100px; height:500px; overflow:scroll; border: hidden">
<syntaxhighlight lang="cpp" style="border: none; background-color: #EFF1C1; font-size:larger">
/* Includes_BEGIN */
#ifndef MATLAB_MEX_FILE
#include "HX711.h"
#define OFFSET 66981 //Calibration Value
#define SCALE (float) (145173 - OFFSET) / 200 //Scaling factor of raw Values
HX711 loadCell;
#endif
/* Includes_END */
/* Externs_BEGIN */
/* extern double func(double a); */
/* Externs_END */
void read_HX711_Start_wrapper(real_T *xD,
                              const uint8_T *SCK, const int_T p_width0,
                              const uint8_T *DOUT, const int_T p_width1)
{
/* Start_BEGIN */
#ifndef MATLAB_MEX_FILE
    //Initialisation of LoadCell
    loadCell.begin((int) DOUT[0], (int) SCK[0]);
    loadCell.set_offset(OFFSET);
    loadCell.set_scale(SCALE);
#endif
/* Start_END */
}
void read_HX711_Outputs_wrapper(real_T *force,
                                const real_T *xD,
                                const uint8_T *SCK, const int_T p_width0,
                                const uint8_T *DOUT, const int_T p_width1)
{
/* Output_BEGIN */
#ifndef MATLAB_MEX_FILE
    //Read force
    float f = loadCell.get_units(10);
    force[0] = (double) f;
#endif
/* Output_END */
}
void read_HX711_Update_wrapper(real_T *force,
                              real_T *xD,
                              const uint8_T *SCK, const int_T p_width0,
                              const uint8_T *DOUT, const int_T p_width1)
{
/* Update_BEGIN */
/* Update_END */
}
void read_HX711_Terminate_wrapper(real_T *xD,
                                  const uint8_T *SCK, const int_T p_width0,
                                  const uint8_T *DOUT, const int_T p_width1)
{
/* Terminate_BEGIN */
/*
* Custom Terminate code goes here.
*/
/* Terminate_END */
}
</syntaxhighlight>
</div>
'''drive_Steps S-Function:'''
<div style="width:1100px; height:500px; overflow:scroll; border: hidden">
<syntaxhighlight lang="cpp" style="border: none; background-color: #EFF1C1; font-size:larger">
/* Includes_BEGIN */
#ifndef MATLAB_MEX_FILE
#include "DM442.h"
DM442 stepper;
#endif
/* Includes_END */
/* Externs_BEGIN */
/* extern double func(double a); */
/* Externs_END */
void drive_Steps_Start_wrapper(real_T *xD,
                              const uint8_T *ENA, const int_T p_width0,
                              const uint8_T *DIR, const int_T p_width1,
                              const uint8_T *PUL, const int_T p_width2)
{
/* Start_BEGIN */
#ifndef MATLAB_MEX_FILE
    //Initialise and start Steppermotor
    stepper.begin(DIR[0], ENA[0], PUL[0]);
    stepper.setDirection(RIGHT);
    stepper.setFrequency(30);
    stepper.start();
#endif
/* Start_END */
}
void drive_Steps_Outputs_wrapper(const uint16_T *stepsToDrive,
                                uint16_T *stepsDriven,
                                const real_T *xD,
                                const uint8_T *ENA, const int_T p_width0,
                                const uint8_T *DIR, const int_T p_width1,
                                const uint8_T *PUL, const int_T p_width2)
{
/* Output_BEGIN */
#ifndef MATLAB_MEX_FILE
    //Drive x-Steps in 1 second
    stepper.setFrequency(stepsToDrive[0]);
    stepper.driveSteps(stepsToDrive[0]);
    long steps = stepper.getSteps();
    stepsDriven[0] = steps;
#endif
/* Output_END */
}
void drive_Steps_Update_wrapper(const uint16_T *stepsToDrive,
                                uint16_T *stepsDriven,
                                real_T *xD,
                                const uint8_T *ENA, const int_T p_width0,
                                const uint8_T *DIR, const int_T p_width1,
                                const uint8_T *PUL, const int_T p_width2)
{
/* Update_BEGIN */
/* Update_END */
}
void drive_Steps_Terminate_wrapper(real_T *xD,
                                  const uint8_T *ENA, const int_T p_width0,
                                  const uint8_T *DIR, const int_T p_width1,
                                  const uint8_T *PUL, const int_T p_width2)
{
/* Terminate_BEGIN */
/*
* Custom Terminate code goes here.
*/
/* Terminate_END */
}
</syntaxhighlight>
</div>
'''Arduino Regler:'''
<div style="width:1100px; height:500px; overflow:scroll; border: hidden">
<syntaxhighlight lang="cpp" style="border: none; background-color: #EFF1C1; font-size:larger">
#include "HX711.h"
#include "DM442.h"
//Debug Funktionen
//#define DEBUG
//#define DEBUG_PID
// Definitionen für die Konfiguration
#define OFFSET 66981
#define SCALE (float) (145173 - OFFSET) / 200
#define MAX_FILL_LEVEL 500
#define ANGLE_PER_STEP (float) 0.39
#define ROUND_OFFSET 0.1f
#define KP 0.014
#define KI 0.00675
#define KD 0
#define PID_MAX 30
#define PID_MIN 0
#define START_SWITCH 2
HX711 loadCell;
DM442 stepper;
int arrayLength = 50;
//Daten für Lookuptable
float forceArray[50] = {-590.41, -574.05, -554.12, -534.25, -514.57, -494.57, -474.99, -455.52, -436.21, -417.06, -389.06, -379.36, -360.83, -342.52, -324.58, -306.87, -289.52, -272.52, -255.77, -239.32, -223.36, -207.89, -192.75, -178.08, -162.85, -150.12, -136.92, -124.11, -111.74, -99.90, -88.64, -77.87, -67.81, -58.80, -49.67, -41.64, -34.24, -27.44, -21.46, -15.85, -11.08, -7.04, -3.68, -0.94, 1.15, 2.47, 3.17};
double angleArray[50] = {90, 88.05, 86.10, 84.15, 82.20, 80.25, 78.30, 76.35, 74.40, 72.45, 70.50, 68.55, 66.60, 64.65, 62.70, 60.75, 58.80, 56.85, 54.90, 52.95, 51.00, 49.05, 47.10, 45.15, 43.20, 41.25, 39.30, 37.35, 35.40, 33.45, 31.50, 29.55, 27.60, 25.65, 23.70, 21.75, 19.80, 17.85, 15.90, 13.95, 12.00, 10.05, 8.10, 6.15, 4.20, 2.25, 0.30, 0};
// Initialisierung der Lastzelle
void setupHX711() {
  loadCell.begin(8, 9);
  loadCell.set_offset(OFFSET);
  loadCell.set_scale(SCALE);
}
// Initialisierung des Schrittmotors
void setupDM442() {
  stepper.begin(3, 4, 5);
  stepper.setDirection(RIGHT);
  stepper.setFrequency(30);
  stepper.start();
}
// Aufruf der Setup-Funktionen und Initialisierung des seriellen Ports
void setup() {
  setupDM442();
  setupHX711();
  pinMode(START_SWITCH, INPUT_PULLUP);
  #if defined(DEBUG) || defined(DEBUG_PID)
    Serial.begin(9600);
  #endif
}
bool run = true;
void loop() {
  static float angle = 90;
  //Schalter AUS resette das Programm
  if(digitalRead(START_SWITCH) != 0) {
    angle = 90;
    stepper.resetSteps();
    pid(0, true);
    return;
  }
  if(angle < 0) return; //Für sporadische Messunsicherheiten
  #ifdef DEBUG 
    Serial.print("ANGLE:");
    Serial.print(angle);
    Serial.print(",");
  #endif
  //Messe die anliegende Kraft
  float force = loadCell.get_units(10);
  #ifdef DEBUG 
    Serial.print("FORCE:");
    Serial.print(force);
    Serial.print(",");
  #endif
  double cosVal = cos(angle * 3.14 / 180);
  //Durch Lookuptable die genullte Kraft bestimmen
  double zeroForce = interpolateY(angleArray, forceArray, arrayLength, angle);
  #ifdef DEBUG 
    Serial.print("ZERO:");
    Serial.print(zeroForce);
    Serial.print(",");
  #endif
  //Die Kraft differenz zwischen Nullkraft und anliegender Kraft
  double dF = force - zeroForce;
  #ifdef DEBUG 
    Serial.print("dF:");
    Serial.print(dF);
    Serial.print(",");
  #endif
  //Berechne die senkrechte Kraft -> Füllstand
  double fillLevel =  cosVal * dF;
  #if defined(DEBUG) || defined(DEBUG_PID)
    Serial.print("Level:");
    Serial.print(fillLevel);
    Serial.print(",");
  #endif
  //Berechne die Differenz zum vollen Glas
  double error = MAX_FILL_LEVEL - fillLevel;
  #ifdef DEBUG_PID 
    Serial.print("ERROR:");
    Serial.print(error);
    Serial.print(",");
  #endif
  //Benutze PID-Regler um Schritte für Motor zu ermitteln
  int steps = (int) pid(error, false);
  #ifdef DEBUG_PID
    Serial.print("STEPS:");
    Serial.print(steps);
    Serial.print(",");
  #endif
  //Fahre in einer Sekunde die vorgegebenen Schritte
  stepper.setFrequency(steps);
  stepper.driveSteps(steps);
  //Speichere die insgesamt gefahrenen Schritte abe
  unsigned long drivenSteps = stepper.getSteps();
 
  #ifdef DEBUG_PID 
    Serial.print("DRIVEN:");
    Serial.print(drivenSteps);
  #endif
  //Ziehe vom Startwinkel den insgesamt gefahrenen Winkel ab
  angle = 90 - drivenSteps * ANGLE_PER_STEP;
  Serial.println();
}
//Umsetzung eines PID-Reglers
int pid(double error, bool reset) {
  static unsigned long startMillis = 0;
  long iterationTime = millis() - startMillis;
  static double errorSum = 0;
  static double lastError = 0;
 
  if(reset) {
    startMillis = millis();
    errorSum = 0;
    lastError = 0;
    return 0;
  }
  errorSum += error;
  double d = (error - lastError) / iterationTime;
  lastError = error;
  double regulated = KP * error + KI * errorSum / iterationTime + KD * d;
  #ifdef DEBUG_PID 
    Serial.print("PID:");
    Serial.print(regulated);
    Serial.print(",");
  #endif
  int out = roundTo(regulated);
  if(out > PID_MAX) return PID_MAX;
  else if(out < PID_MIN) return PID_MIN;
  else return out;
}
//Interpolationsfunktion als Look-up Table
float interpolateY(double X[], float Y[], int n, double X_interpolated) {
    for (int i = 0; i < n - 1; i++) {
        if (X[i] <= X_interpolated && X_interpolated <= X[i + 1]) {
            double X1 = X[i];
            double X2 = X[i + 1];
            float Y1 = Y[i];
            float Y2 = Y[i + 1];
            return (float) ( Y1 + ((Y2 - Y1) / (X2 - X1)) * (X_interpolated - X1));
        }
    }
    return 0.0;
}
//Rundet nicht bei 0.5 sondern bei 0.1
int roundTo(float x) {
  float diff = x - (int) x;
  if(diff > ROUND_OFFSET) return (int) x + 1;
  else return (int) x;
}
</syntaxhighlight>
</div>


== Komponententest ==
== Komponententest ==
{| class="wikitable"
|+ style = "text-align: left"|Teilanforderungen
! style="font-weight: bold;" | Nr.
! style="font-weight: bold;" | Beschreibung
! style="font-weight: bold;" | Testmethode
! style="font-weight: bold;" | Zuständigkeit
! style="font-weight: bold;" | Testergebnis
|-
| 1
| Die Halterungen müssen an der Holzplatte befestigt werden.
| Die Halterungen wurden korrekt befestigt und halten dem Gewicht und der Rotation stand.
| Fleer, Sander
| Bestanden
|-
| 2
| Der Motor muss die Holzplatte drehen können.
| Der Motor kann sowohl beide Gewichte als auch nur die Flasche ohne Probleme heben und halten.
| Fleer, Sander
| Bestanden
|-
| 3
| Die Elektrik muss eingebaut und verkabelt werden.
| Die Verdrahtung der Bauteile wurde korrekt durchgeführt und nochmals mittels Durchgangsprüfung geprüft.
| Fleer, Sander
| Bestanden
|-
| 4
| Die Regelung muss durch den Kraftsensor erfolgen.
| Die Wägezelle misst jede Sekunde die Gewichtskraft des Inhalts. Dieser Ist-Wert wird mit dem Soll-Wert subtrahiert und geht als Regelfehler in unseren Regler.
| Fleer, Sander
| Bestanden
|-
| 5
| Der Motor muss mittels Regler angesteuert werden
| Die Ansteuerung erfolgt softwareseitig durch die Stellgröße unseres Reglers.
| Fleer, Sander
| Bestanden
|-
|}


== Ergebnis ==
== Ergebnis ==
Das Projekt zur Füllstandsregelung eines Bierglases an der HSHL im Fachgebiet der Mechatronik führte zu einem positiven Ergebnis. Die Anlage ist in der Lage durch umlegen eines Schalters ein 0,5Liter Bierglas einzuschenken. Zu Beginn des Einfüllvorgangs werden die maximalen Motorschritte gefahren. Sobald der Füllstand erkannt wird, werden, je nach Füllstand, weniger Schritte gefahren.
<gallery widths="500" heights="500">
Datei:Frontansicht Aufbau.jpg| Frontansicht der Weizeneingießanlage
</gallery>
<gallery widths="500" heights="500">
Datei:Rueckansicht Aufbau.jpg| Rückansicht der Weizeneingießanlage
</gallery>


== Zusammenfassung ==
== Zusammenfassung ==
=== Lessons Learned ===
=== Lessons Learned ===
Wir haben am Anfang viele Überlegungen getroffen. Aufgrund von Berechnungsfehlern oder falschen Datenblättern haben wir viele Rükschläge erleiden müssen. Schlussendlich konnten wir unseren Anfangs geplanten Ablauf nicht in die Tat umsetzen und sind ziehmlich in Zeitstress gekommen. Daraus haben wir gelernt jedes Datenblatt zweimal zu überprüfen und Berechnungen auch in der Realität auszuprobieren und nicht auf theoretische Annahmen zu vertrauen.
Auch die Programmierung mit Simulink war eine Tortur. Aufgrund unseres Zeitdrucks haben wir uns dann dazu entschieden erstmal ein Arduino Programm zu entwickeln, womit wir immerhin schonmal alles testen konnten und weiter kamen. Später haben wir dann die Reglereinstellungen und Ansteuerungen für Sensor und Aktor in S-Function-Blöcke übernommen und so ein lauffähiges Simulink Modell kreiert. Die Schlüsse die wir hier raus gezogen haben, sind erstmal den vertrauten einfachen Weg gehen und später erst den unkonventionellen Weg nehmen. Auch haben wir gelernt nie wieder auf Matlab Simulink etwas für einen Arduino zu programmieren, da dies um ein viel faches schwieriger ist als ein sauber aufgebautes Arduino Projekt.
In der Programmierung gab es noch ein weiteres Problem. Für den HX711 gab es eine lauffähige Bibliothek schon zur Verfügung. Da wir nicht auf den Anfangs geplanten Motor von Funduino zurück greifen konnten mussten wir einen anderen Motor nehmen. Da dieser Motor eine Anfertigung für die Firma Diebold Nixdorf ist gibt es keine öffentlichen Bibliotheken zur Steuerung. Somit musste hier eine eigene Bibliothek zur Steuerung programmiert werden. Diese musste dann auch wieder getestet und Fehler behoben werden. Auch hier wäre mit mehr Zeit es einfacher gewesen einen Motor zu beschaffen der schon eine fertige Bibliothek öffentlich verfügbar hat.


== Projektunterlagen ==
== Projektunterlagen ==
=== Projektplan ===
=== Projektplan ===
<gallery widths="900" heights="450">
Datei:GantChartWeizenglasanlage.png| Projektplan
</gallery>
=== Projektdurchführung ===
=== Projektdurchführung ===
Dieses Projekt ist Teil des Moduls GET-Fachpraktikum. Dieses Projekt konzentriert sich auf die Regelung und Ansteuerung eines Motors zur Füllstandsregelung eines 0,5 Liter Glases.
Zu Beginn des Projektes ging es vorwiegend um die Konzeptplanung und die damit verbundenen benötigten Bauteile. Als nächstes wurden die benötigten Bauteile beschafft und auf ihre Eignung geprüft. Des Weiteren wurden die einzelnen Komponenten zu einem Gesamtsystem zusammengebaut und die Funktion des Motors getestet. Dabei fiel auf, dass wir einen stärkeren Motor für unser System benötigten. Nach Einbindung des neuen Motors in das System begannen wir damit die Messwerte zu klassifizieren und den Regler auszulegen. Abschließend legten wir diese Dokumentation im HSHL Wiki an.
=== Projektdaten ===
ZIP-Archiv: [[Datei:165 Weizen Eingiessanlage.zip|mini]] <br clear="all">


== YouTube Video ==
== YouTube Video ==
In dem folgenden Youtube Video [https://youtu.be/AbGATzM_GaI YouTube-Video] werden die Funktionen Funktion der Weizeneingießanlage gezeigt.
{{#ev:youtube|https://youtu.be/AbGATzM_GaI| 750 | | Video 1: Funktion der Weizeneingießanlage |frame}}


== Weblinks ==
== Weblinks ==


== Literatur ==
== Literatur ==





Aktuelle Version vom 18. Januar 2024, 00:34 Uhr

Autor: Philipp Sander, Dennis Fleer
Betreuer: Prof. Dr. Mirek Göbel


Einleitung

Dieser Artikel beschreibt den Aufbau und die Funktion einer Weizenglas-Eingießanlage. Hierbei gibt es eine Halterung für die Flasche und eine für ein Glas. Beide sind jeweils an einer rotierenden Holzplatte montiert. Diese Holzplatte wird mittels Elektromotor rotiert. Über einem Drucksensor wird der Füllstand des Weizenglases ermittelt und eine optimale Einfüllung des Glases erreicht.

Anforderungen

Teilanforderungen
Nr. Beschreibung Bereich Zuständig
1 Die Halterungen müssen an der Holzplatte befestigt werden. Hardware Dennis Fleer, Philipp Sander
2 Der Motor muss die Holzplatte drehen können. Hardware Dennis Fleer, Philipp Sander
3 Die Elektrik muss eingebaut und verkabelt werden. Hardware Dennis Fleer, Philipp Sander
4 Die Regelung muss durch den Kraftsensor erfolgen. Software Dennis Fleer, Philipp Sander
5 Der Motor muss mittels Regler angesteuert werden. Software Dennis Fleer, Philipp Sander

Funktionaler Systementwurf/Technischer Systementwurf

Im folgenden Systementwurf wird das Projekt in Systemkomponenten unterteilt:

  • Arduino: In Matlab Simulink programmierte Ansteuerung des Motors und Regelung.
  • Motor: Drehbewegung der Holzplattform
  • Drucksensor: Kraftmessung für die Füllstandserkennung
  • Regler: Füllstandsregelung des Weizenglases


Ansatz zur Herleitung der Übertragungsfunktion:

Komponentenspezifikation

Nr. Bauteil Beschreibung
1 Arduino MEGA 2560
  • Versorgungsspannung: 7V-12V (empfohlen)
  • Betriebsspannung: 5V
  • 54 digitale Ein-/Ausgänge und 16 analoge Eingänge
2 DM442 Motortreiber
  • Vollschritt Einstellung
  • Versorgungsspannung 24V
3 HX711 Wägezelle
  • Versorgungsspannung 5V
  • Messbereich: bis zu 5Kg
  • 24 Bit ADC
4.1 Schrittmotor ...
  • Betriebsspannung 3V
  • Haltedrehmoment mind. 1,6 Nm

Umsetzung (HW/SW)

Hardware

Projektübersicht:

In unserem Projekt liegen die Anforderung vorrangig darin eine motorbetriebene drehbare Holzplatte zu konstruieren und dieses System mit einer geeigneten Motoransteuerung zu betreiben. In bezug auf Kosten und Gewicht haben wir uns daher für eine Holzkonstruktion entschieden. Die Holzplatte, an welcher die Halterungen für die Bierflasche und Weizenflasche sowie der Sensor befestigt ist, wird von einer Massivholzwelle getragen. Diese Welle wird mittels Lageraufständen auf einer Holzgrundplatte befestigt. Die Welle wird durch eine Riemenübersetzung mit dem Schrittmotor angetrieben.

3D Ansicht:

Baugruppen Model

Motor:

Wir wollten zunächst den Motor des Arduino-Mega-Sets verwenden. Allerdings ist uns nach einer kleinen Testfahrt aufgefallen dass dieser zu wenig Haltedrehmoment aufweist. Daher haben wir auf einen ausgediehnten Motor aus unsere Partnerfirma "Diebold-Nixdorf Inc." gesetzt. Dieser Motor war nun auch in der Lage unsere Anforderungen zu erfüllen. Um diesen Motor zu betreiben wählten wir einen Motortreiber welchen wir ebenfalls in einem ausgediehnten Geldautomaten-System fanden.

Konstruktion:

Die Konstruktion der Halterung ist auf den ersten Blick sehr simpel. Dennoch galt es für uns die Kosten sowie das Gewicht niedrig zu halten. Daher belaufen sich fast alle Komponenten auf Holz und Kunststoff. Für die meisten Komponenten konnten wir auf unser Betriebsinternes Lager zurückgreifen lediglich die Holzplatte und die Halterungen für Flasche und Glas waren noch nicht vorhanden. Die Halterungen für Glas und Flasche wurden durch einen 3D-Kunststoffdruck gefertigt. Zudem mussten wir uns überlegen wie wir die Flasche in der Halterung befestigen. Um auch hier eine günstige, einfache und leichte Lösung zu erreichen, setzen wir auf die Spannkraft zweier Gummis, welche die Flasche im Halter halten.

Wägezelle HX711:

Messprinzip: Durch das zu messende Gewicht wird die Wägezelle leicht gebogen. An der Seite befinden sich 4 Dehnungsmesstreifen, welche als Wheatsstone Brücke angeordnet sind. Der Widerstand der Messstreifen ändert sich mit dem Dehnungsgrad. Somit ändert sich bei einer Last der Widerstand und damit auch der Spannungsabfall der Brückenschaltung. Der Spannungsabfall zwischen den Punkten A und B wird mit einem A/D Wandler ausgewertet.


Da die Wägezelle über die physikalische Kraft der Gewichtskraft funktioniert, war es eine Herausforderung in unserem drehenden System die korrekten Werte für den Füllstand zu erhalten. Dies war deshalb so schwierig weil sich die Wägezelle immer in einem anderen Winkel zur Gewichtskraft des Füllstandes befindet. Aus diesem Grund mussten wir die Messwerte noch einmal mit dem cos des Winkels multiplizierten um die korrekte Gewichtskraft zu erhalten. Wir verwenden außerdem einen Look-up-Table um uns die Verarbeitung der Messdaten zu vereinfachen. Konkret subtrahieren wir unseren gemessenen wert mit dem Wert aus dem Look-up-Table an der gleichen Winkelstellung. Somit Erhalten wir die korrekte Gewichtskraft des Pegelstandes des Glases. Dies ist dann dementsprechend unser IST-Wert.

PI-Regler:

Der IST-Wert wird nun mit dem SOLL-Wert von 500 (500ml/Kg) subtrahiert. Somit erhalten wir unsere Regelabweichung welche wir weiterhin mit dem PI-Regler verarbeiten. Aufgrund dessen, dass 1-Motorschritt in 0,39 Grad am äußeren Umfang der Holzplatte entspricht, benötigen wir eine sehr schnelle Regulierung der Motorschritte, sobald Flüssgkeit in das Glas läuft. Daher benötigen wir eine möglichst schnelle Sprungantwort und somit einen großen P-Anteil in unserem Regler. Für unser System haben sich die Werte Kp=0,014 und Ki=0,00675 als praktikabel erwiesen. Die Stellgröße des Reglers wird direkt als Ansteuerung für den Motor genutzt.

Motoransteuerung:

Die Motoransteuerung erfolgt über den Motortreiber DM441 dieser braucht eine externe Versorgungsspannung von 24V. Außerdem werden zur Steuerung drei Pins angeschlossen.

- ENA: Sorgt für das Starten des Motors und zusätzlich auch für einen Haltestrom.

- DIR: Gibt die Richtung, in die der Motor fahren soll.

- PUL: Über diesen Pin wird ein PWM-Signal gesendet, welches das Drehmoment und die Geschwindigkeit bestimmt.

Verdrahtungsplan:

Software

Programmablaufplan:

Simulink Regler Model:

Berechnung der Regelabweichung Subsystem
PID-Regler Subsystem

DM442.h:

#ifndef DM442_h
#define DM442_h

#define RIGHT (bool) true
#define LEFT (bool) false

//Motor Driver Leadshine DM442
class DM442
{
public:
	void begin(int dir, int ena, int pul);
	void start();
	void stop();

	//Use the defines for setting the direction
	void setDirection(bool direction); //sets the rotating direction
	void setFrequency(int stepsPerSecond); //sets the speed
	void setDutyCycle(int percent); //sets the torque

	void resetSteps(); //Reset of counted steps

	void singleStep(); //Drive one step
	void driveSteps(int steps); //Drive many steps

	long getSteps(); //Get driven steps

private:
	int dir; //Direction-Pin address
	int ena; //Enable-Pin address
	int pul; //Pulse-Pin address
	int frequency; //highs per second
	int dutyCycle; //in %
	bool running; //Motor can drive
	bool direction; //motor rotates in direction TRUE = right | FALSE = left viewed from the front 
	long steps; //positive value for right steps | negative for left steps

};

#endif

DM442.cpp:

#include "DM442.h"
#include "Arduino.h"

void DM442::begin(int dir, int ena, int pul) {
	this->dir = dir;
	this->ena = ena;
	this->pul = pul;
	pinMode(dir, OUTPUT);
	pinMode(ena, OUTPUT);
	pinMode(pul, OUTPUT);
	this->steps = 0;
	this->running = false;
	this->direction = RIGHT;
	this->dutyCycle = 50;
	this->frequency = 1;
	TCCR4B = TCCR4B & 0b11111000 | 0x01;
}

void DM442::start() {
	digitalWrite(this->ena, LOW);
}

void DM442::stop() {
	digitalWrite(this->ena, HIGH);
}

void DM442::setDirection(bool direction) {
	this->direction = direction;
	if (direction) digitalWrite(this->dir, HIGH);
	else digitalWrite(this->dir, LOW);
}

void DM442::setFrequency(int stepsPerSecond) {
	this->frequency = stepsPerSecond;
}

void DM442::setDutyCycle(int percent) {
	this->dutyCycle = percent;
}

void DM442::singleStep() {
	//analogWrite(this->pul, 255 * this->dutyCycle / 100);
	if (this->frequency == 0) return;
	long delayHigh = (float) this->dutyCycle / 100 * 500 / this->frequency;
	long delayLow = (float) (100 - this->dutyCycle) / 100 * 500 / this->frequency;
	digitalWrite(this->pul, HIGH);
	delay(delayHigh);
	digitalWrite(this->pul, LOW);
	delay(delayLow);
	this->steps++;
}

void DM442::driveSteps(int steps) {
	int percentPerStep = 40 / steps;
	for (int i = 0; i < steps; i++) {
		setDutyCycle(10 + i * percentPerStep);
		singleStep();
	}
}

long DM442::getSteps() {
	return this->steps;
}

void DM442::resetSteps() {
	this->steps = 0;
}

read_HX711 S-Function:

/* Includes_BEGIN */
#ifndef MATLAB_MEX_FILE

#include "HX711.h"

#define OFFSET 66981 //Calibration Value
#define SCALE (float) (145173 - OFFSET) / 200 //Scaling factor of raw Values

HX711 loadCell;

#endif
/* Includes_END */

/* Externs_BEGIN */
/* extern double func(double a); */
/* Externs_END */

void read_HX711_Start_wrapper(real_T *xD,
                              const uint8_T *SCK, const int_T p_width0,
                              const uint8_T *DOUT, const int_T p_width1)
{
/* Start_BEGIN */
#ifndef MATLAB_MEX_FILE
    //Initialisation of LoadCell
    loadCell.begin((int) DOUT[0], (int) SCK[0]);
    loadCell.set_offset(OFFSET);
    loadCell.set_scale(SCALE);
#endif
/* Start_END */
}

void read_HX711_Outputs_wrapper(real_T *force,
                                const real_T *xD,
                                const uint8_T *SCK, const int_T p_width0,
                                const uint8_T *DOUT, const int_T p_width1)
{
/* Output_BEGIN */
#ifndef MATLAB_MEX_FILE
    //Read force
    float f = loadCell.get_units(10);
    force[0] = (double) f;
#endif
/* Output_END */
}

void read_HX711_Update_wrapper(real_T *force,
                               real_T *xD,
                               const uint8_T *SCK, const int_T p_width0,
                               const uint8_T *DOUT, const int_T p_width1)
{
/* Update_BEGIN */

/* Update_END */
}

void read_HX711_Terminate_wrapper(real_T *xD,
                                  const uint8_T *SCK, const int_T p_width0,
                                  const uint8_T *DOUT, const int_T p_width1)
{
/* Terminate_BEGIN */
/*
 * Custom Terminate code goes here.
 */
/* Terminate_END */
}

drive_Steps S-Function:

/* Includes_BEGIN */
#ifndef MATLAB_MEX_FILE

#include "DM442.h"

DM442 stepper;

#endif
/* Includes_END */

/* Externs_BEGIN */
/* extern double func(double a); */
/* Externs_END */

void drive_Steps_Start_wrapper(real_T *xD,
                               const uint8_T *ENA, const int_T p_width0,
                               const uint8_T *DIR, const int_T p_width1,
                               const uint8_T *PUL, const int_T p_width2)
{
/* Start_BEGIN */
#ifndef MATLAB_MEX_FILE
    //Initialise and start Steppermotor
    stepper.begin(DIR[0], ENA[0], PUL[0]);
    stepper.setDirection(RIGHT);
    stepper.setFrequency(30);
    stepper.start();
#endif
/* Start_END */
}

void drive_Steps_Outputs_wrapper(const uint16_T *stepsToDrive,
                                 uint16_T *stepsDriven,
                                 const real_T *xD,
                                 const uint8_T *ENA, const int_T p_width0,
                                 const uint8_T *DIR, const int_T p_width1,
                                 const uint8_T *PUL, const int_T p_width2)
{
/* Output_BEGIN */
#ifndef MATLAB_MEX_FILE
    //Drive x-Steps in 1 second
    stepper.setFrequency(stepsToDrive[0]);
    stepper.driveSteps(stepsToDrive[0]);
    long steps = stepper.getSteps();
    stepsDriven[0] = steps;
#endif
/* Output_END */
}

void drive_Steps_Update_wrapper(const uint16_T *stepsToDrive,
                                uint16_T *stepsDriven,
                                real_T *xD,
                                const uint8_T *ENA, const int_T p_width0,
                                const uint8_T *DIR, const int_T p_width1,
                                const uint8_T *PUL, const int_T p_width2)
{
/* Update_BEGIN */

/* Update_END */
}

void drive_Steps_Terminate_wrapper(real_T *xD,
                                   const uint8_T *ENA, const int_T p_width0,
                                   const uint8_T *DIR, const int_T p_width1,
                                   const uint8_T *PUL, const int_T p_width2)
{
/* Terminate_BEGIN */
/*
 * Custom Terminate code goes here.
 */
/* Terminate_END */
}

Arduino Regler:

#include "HX711.h"
#include "DM442.h"

//Debug Funktionen
//#define DEBUG
//#define DEBUG_PID

// Definitionen für die Konfiguration
#define OFFSET 66981
#define SCALE (float) (145173 - OFFSET) / 200
#define MAX_FILL_LEVEL 500
#define ANGLE_PER_STEP (float) 0.39
#define ROUND_OFFSET 0.1f

#define KP 0.014
#define KI 0.00675
#define KD 0
#define PID_MAX 30
#define PID_MIN 0

#define START_SWITCH 2

HX711 loadCell;
DM442 stepper;

int arrayLength = 50;
//Daten für Lookuptable
float forceArray[50] = {-590.41, -574.05, -554.12, -534.25, -514.57, -494.57, -474.99, -455.52, -436.21, -417.06, -389.06, -379.36, -360.83, -342.52, -324.58, -306.87, -289.52, -272.52, -255.77, -239.32, -223.36, -207.89, -192.75, -178.08, -162.85, -150.12, -136.92, -124.11, -111.74, -99.90, -88.64, -77.87, -67.81, -58.80, -49.67, -41.64, -34.24, -27.44, -21.46, -15.85, -11.08, -7.04, -3.68, -0.94, 1.15, 2.47, 3.17};
double angleArray[50] = {90, 88.05, 86.10, 84.15, 82.20, 80.25, 78.30, 76.35, 74.40, 72.45, 70.50, 68.55, 66.60, 64.65, 62.70, 60.75, 58.80, 56.85, 54.90, 52.95, 51.00, 49.05, 47.10, 45.15, 43.20, 41.25, 39.30, 37.35, 35.40, 33.45, 31.50, 29.55, 27.60, 25.65, 23.70, 21.75, 19.80, 17.85, 15.90, 13.95, 12.00, 10.05, 8.10, 6.15, 4.20, 2.25, 0.30, 0};

// Initialisierung der Lastzelle
void setupHX711() {
  loadCell.begin(8, 9);
  loadCell.set_offset(OFFSET);
  loadCell.set_scale(SCALE);
}

// Initialisierung des Schrittmotors
void setupDM442() {
  stepper.begin(3, 4, 5);
  stepper.setDirection(RIGHT);
  stepper.setFrequency(30);
  stepper.start();
}

// Aufruf der Setup-Funktionen und Initialisierung des seriellen Ports
void setup() {
  setupDM442();
  setupHX711();
  pinMode(START_SWITCH, INPUT_PULLUP);
  #if defined(DEBUG) || defined(DEBUG_PID)
    Serial.begin(9600);
  #endif
}

bool run = true;

void loop() {
  static float angle = 90;

  //Schalter AUS resette das Programm
  if(digitalRead(START_SWITCH) != 0) {
    angle = 90;
    stepper.resetSteps();
    pid(0, true);
    return;
  } 

  if(angle < 0) return; //Für sporadische Messunsicherheiten
  #ifdef DEBUG  
    Serial.print("ANGLE:");
    Serial.print(angle);
    Serial.print(",");
  #endif
  //Messe die anliegende Kraft
  float force = loadCell.get_units(10);
  #ifdef DEBUG  
    Serial.print("FORCE:");
    Serial.print(force);
    Serial.print(",");
  #endif
  double cosVal = cos(angle * 3.14 / 180);
  //Durch Lookuptable die genullte Kraft bestimmen
  double zeroForce = interpolateY(angleArray, forceArray, arrayLength, angle);
  #ifdef DEBUG  
    Serial.print("ZERO:");
    Serial.print(zeroForce);
    Serial.print(",");
  #endif
  //Die Kraft differenz zwischen Nullkraft und anliegender Kraft
  double dF = force - zeroForce;
  #ifdef DEBUG  
    Serial.print("dF:");
    Serial.print(dF);
    Serial.print(",");
  #endif
  //Berechne die senkrechte Kraft -> Füllstand
  double fillLevel =  cosVal * dF;
  #if defined(DEBUG) || defined(DEBUG_PID) 
    Serial.print("Level:");
    Serial.print(fillLevel);
    Serial.print(",");
  #endif
  //Berechne die Differenz zum vollen Glas
  double error = MAX_FILL_LEVEL - fillLevel;
  #ifdef DEBUG_PID  
    Serial.print("ERROR:");
    Serial.print(error);
    Serial.print(",");
  #endif
  //Benutze PID-Regler um Schritte für Motor zu ermitteln
  int steps = (int) pid(error, false);
  #ifdef DEBUG_PID 
    Serial.print("STEPS:");
    Serial.print(steps);
    Serial.print(",");
  #endif
  //Fahre in einer Sekunde die vorgegebenen Schritte
  stepper.setFrequency(steps);
  stepper.driveSteps(steps);
  //Speichere die insgesamt gefahrenen Schritte abe
  unsigned long drivenSteps = stepper.getSteps();
  
  #ifdef DEBUG_PID  
    Serial.print("DRIVEN:");
    Serial.print(drivenSteps);
  #endif
  //Ziehe vom Startwinkel den insgesamt gefahrenen Winkel ab
  angle = 90 - drivenSteps * ANGLE_PER_STEP;
  Serial.println();
}

//Umsetzung eines PID-Reglers
int pid(double error, bool reset) {
  static unsigned long startMillis = 0;
  long iterationTime = millis() - startMillis;
  static double errorSum = 0;
  static double lastError = 0;
  
  if(reset) {
    startMillis = millis();
    errorSum = 0;
    lastError = 0;
    return 0;
  }

  errorSum += error;
  double d = (error - lastError) / iterationTime;
  lastError = error;

  double regulated = KP * error + KI * errorSum / iterationTime + KD * d;
  #ifdef DEBUG_PID  
    Serial.print("PID:");
    Serial.print(regulated);
    Serial.print(",");
  #endif
  int out = roundTo(regulated);
  if(out > PID_MAX) return PID_MAX;
  else if(out < PID_MIN) return PID_MIN;
  else return out;
}

//Interpolationsfunktion als Look-up Table
float interpolateY(double X[], float Y[], int n, double X_interpolated) {
    for (int i = 0; i < n - 1; i++) {
        if (X[i] <= X_interpolated && X_interpolated <= X[i + 1]) {
            double X1 = X[i];
            double X2 = X[i + 1];
            float Y1 = Y[i];
            float Y2 = Y[i + 1];
            return (float) ( Y1 + ((Y2 - Y1) / (X2 - X1)) * (X_interpolated - X1));
        }
    }
    return 0.0;
}

//Rundet nicht bei 0.5 sondern bei 0.1
int roundTo(float x) {
   float diff = x - (int) x;
   if(diff > ROUND_OFFSET) return (int) x + 1;
   else return (int) x;
}

Komponententest

Teilanforderungen
Nr. Beschreibung Testmethode Zuständigkeit Testergebnis
1 Die Halterungen müssen an der Holzplatte befestigt werden. Die Halterungen wurden korrekt befestigt und halten dem Gewicht und der Rotation stand. Fleer, Sander Bestanden
2 Der Motor muss die Holzplatte drehen können. Der Motor kann sowohl beide Gewichte als auch nur die Flasche ohne Probleme heben und halten. Fleer, Sander Bestanden
3 Die Elektrik muss eingebaut und verkabelt werden. Die Verdrahtung der Bauteile wurde korrekt durchgeführt und nochmals mittels Durchgangsprüfung geprüft. Fleer, Sander Bestanden
4 Die Regelung muss durch den Kraftsensor erfolgen. Die Wägezelle misst jede Sekunde die Gewichtskraft des Inhalts. Dieser Ist-Wert wird mit dem Soll-Wert subtrahiert und geht als Regelfehler in unseren Regler. Fleer, Sander Bestanden
5 Der Motor muss mittels Regler angesteuert werden Die Ansteuerung erfolgt softwareseitig durch die Stellgröße unseres Reglers. Fleer, Sander Bestanden

Ergebnis

Das Projekt zur Füllstandsregelung eines Bierglases an der HSHL im Fachgebiet der Mechatronik führte zu einem positiven Ergebnis. Die Anlage ist in der Lage durch umlegen eines Schalters ein 0,5Liter Bierglas einzuschenken. Zu Beginn des Einfüllvorgangs werden die maximalen Motorschritte gefahren. Sobald der Füllstand erkannt wird, werden, je nach Füllstand, weniger Schritte gefahren.

Zusammenfassung

Lessons Learned

Wir haben am Anfang viele Überlegungen getroffen. Aufgrund von Berechnungsfehlern oder falschen Datenblättern haben wir viele Rükschläge erleiden müssen. Schlussendlich konnten wir unseren Anfangs geplanten Ablauf nicht in die Tat umsetzen und sind ziehmlich in Zeitstress gekommen. Daraus haben wir gelernt jedes Datenblatt zweimal zu überprüfen und Berechnungen auch in der Realität auszuprobieren und nicht auf theoretische Annahmen zu vertrauen.

Auch die Programmierung mit Simulink war eine Tortur. Aufgrund unseres Zeitdrucks haben wir uns dann dazu entschieden erstmal ein Arduino Programm zu entwickeln, womit wir immerhin schonmal alles testen konnten und weiter kamen. Später haben wir dann die Reglereinstellungen und Ansteuerungen für Sensor und Aktor in S-Function-Blöcke übernommen und so ein lauffähiges Simulink Modell kreiert. Die Schlüsse die wir hier raus gezogen haben, sind erstmal den vertrauten einfachen Weg gehen und später erst den unkonventionellen Weg nehmen. Auch haben wir gelernt nie wieder auf Matlab Simulink etwas für einen Arduino zu programmieren, da dies um ein viel faches schwieriger ist als ein sauber aufgebautes Arduino Projekt.

In der Programmierung gab es noch ein weiteres Problem. Für den HX711 gab es eine lauffähige Bibliothek schon zur Verfügung. Da wir nicht auf den Anfangs geplanten Motor von Funduino zurück greifen konnten mussten wir einen anderen Motor nehmen. Da dieser Motor eine Anfertigung für die Firma Diebold Nixdorf ist gibt es keine öffentlichen Bibliotheken zur Steuerung. Somit musste hier eine eigene Bibliothek zur Steuerung programmiert werden. Diese musste dann auch wieder getestet und Fehler behoben werden. Auch hier wäre mit mehr Zeit es einfacher gewesen einen Motor zu beschaffen der schon eine fertige Bibliothek öffentlich verfügbar hat.

Projektunterlagen

Projektplan

Projektdurchführung

Dieses Projekt ist Teil des Moduls GET-Fachpraktikum. Dieses Projekt konzentriert sich auf die Regelung und Ansteuerung eines Motors zur Füllstandsregelung eines 0,5 Liter Glases. Zu Beginn des Projektes ging es vorwiegend um die Konzeptplanung und die damit verbundenen benötigten Bauteile. Als nächstes wurden die benötigten Bauteile beschafft und auf ihre Eignung geprüft. Des Weiteren wurden die einzelnen Komponenten zu einem Gesamtsystem zusammengebaut und die Funktion des Motors getestet. Dabei fiel auf, dass wir einen stärkeren Motor für unser System benötigten. Nach Einbindung des neuen Motors in das System begannen wir damit die Messwerte zu klassifizieren und den Regler auszulegen. Abschließend legten wir diese Dokumentation im HSHL Wiki an.

Projektdaten

ZIP-Archiv: Datei:165 Weizen Eingiessanlage.zip

YouTube Video

In dem folgenden Youtube Video YouTube-Video werden die Funktionen Funktion der Weizeneingießanlage gezeigt.

Video 1: Funktion der Weizeneingießanlage

Weblinks

Literatur


→ zurück zur Übersicht: WS 22/23: Angewandte Elektrotechnik (BSE)