top of page

Project

An automatic yogurt delivery process has been created using dispensers for keeping the toppings and a moveable platform using an stepper motor, all controlled by a system programmed by Arduino.

871ad26c-ce3c-4725-9242-712ebe626760.jpg
Product

Product

The yogurt machine will be used at some  automatized yogurt shop . Firstly the custumer will chose the amount of yogurt, after that, the different toppings available will appear in the application list of the phone where you will be able to choose between the different toppings. Once everything is done, you just have to enjoy the yogurt!

Why this machine?

We chose to create this machine due to the possibilities of implement all the knowledge that we have achived in the degree and learn to solve problems, optimize the budget and more.

Why it is interested?

It is interesting since that machine could work in a yogurt shop or even like the vending machines and the product could commercialize for over the world.

Objectives

The main objective taking into account that we are students, is to learn, especially to work in group and distribute the task between all the members of the team to achieve the specific objectives.

In the specific objectives we have the ones that will grow us as engineers, like to understand the different type of components and how to connect them, also we have the uses of the different tools for creating the structure, to think the best ways to do something.

Planning

Planning

gantt.png
Step by step

Step by step

There’s a list of things we’ve done to finish with the final project,

The first thing that we have done is to choose the project. After some different ideas proposed by the university we have decided to think on our own because the options that were in the list weren’t motivating the team. Once we decided to not use any of the options mentioned before, after a brainstorming we arrived to an idea of what we finally created, The yogurt machine!!

 

When we already had the project idea we could start to move and create! There’s a list of the step by step creation of the machine.

sw.png

01

Simulated Structure

First of all a  3D model has been created using Solidworks for analysing the project before constructing and to choose the best sizes for the creation of the project.

02

Real Structure

Once the different sizes and measures of the structure are clear the body of the project has been constructed using wood since is an easy material for working with and  accessible for students.

wood.jpg
topping.jpg

03

Regulation of toppings

A lot of tests have been done due to wanting to find the best solution for our problem that was to be able to control the amount of toppings  choosed by the customer.

04

Creation of 3D pieces

Different pieces of plastic using a 3D printed machine have been created for different purposes, the supports for the 2 metal bars, a gear and a piece for pushing the dispenser bottom, a support for the recipients and for the motor.

impresora3d.jpg
chromebars.jpg

05

Metal bars

Two metal bars have been used for moving the platform that will contain the yogurt.

In this part is extremly important to situate the 2 metal bars completely in parallel , if not lot of problems will appear.

06

The platform

The platform has been created twice, since the first time the platform that we created wasn't stable for this accurate process, due to that, a second platform was created but this time using the automatic milling machine that the university has for creating something more professional and stable.

transporter.jpg
proto.jpg

07

The electronic board

Before constructing and welding all the components into the electronic board, some electronic tests were done for the components, the connections and the code. Once the tests were done, the welding and connecting all the components started.

For all the connections the it was used the data sheets from the providers.

08

The pump

A pump has been installed for pushing the yogurt.

Some modifications were done for adapt our necessities.

pump2.jpg
logoarduino.jpg

09

The code

The code has been generated after some different tests using some information of the data sheet of the components and some internet information.

MATERIALS

Materials

Here is the list of the components that we have used to create the project.

If you click, there is a link where you could buy the same component that is used in the project.

ELECTRIC SCHEMATIC
Picsart_22-06-01_12-45-57-875.jpg

ELECTRIC SCHEMATIC

The electric schema of the project has been created using Kicad, and developed software for electronic and electric schemas creator.

kicad_edited.png
schematicYogurtMachine_page-0001.jpg
CODE

CODE

The code has been generated using Arduino.

There is all the code below.

1200px-ArduinoLogo_®.svg.png

//LLIBRERIES//
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>

//CONSTANTS//
#define DISPENSER_COUNT 7         //quantitat de dispensadors (6) + Yogurt
#define YOGURT_DISTANCE 950       //distancia yogur
#define DISPENSER_A     1933      //distancia dispensador 1
#define DISPENSER_B     2875      //distancia dispensador 2
#define DISPENSER_C     3835      //distancia dispensador 3
#define DISPENSER_D     4820      //distancia dispensador 4
#define DISPENSER_E     5780      //distancia dispensador 5

#define PUMP 15       //pin de la bomba al pin 15
#define ENDSTOP1 33   //pin final de carrera 1 - Inici recorregut
#define ENDSTOP2 32   //pin final de carrera 2 - Final recorregut
#define ENDSTOP3 35   //pin final de carrera 3 - Inici cremallera    
#define ENABLE 13     //pin ENABLE del DRV8825 al pin 13
#define ENABLE2 25    //pin ENABLE2 del DRV8825 al pin 25
#define STEP 12       //pin STEP del DRV8825 a pin 12
#define DIR 14        //pin DIR de DRV8825 a pin 14
#define STEP2 27      //pin STEP2 del segon driver al pin 27
#define DIR2 26       //pin DIR2 del segon driver al pin 26

//ESTRUCTURA DEL DISPENSADOR//
typedef struct dispenser {
    bool selected;
    String description;
} dispenser;
bool running = false;
dispenser dispensadores[DISPENSER_COUNT]; //llista de dispensadors 
const char * ssid = "Lab-Modul";
const char * password = "dgK672sd";
WebServer server(80);

//FUNCIONS SERVIDOR//
void handle_root();           //Imprimeix la pàgina principal
void handle_not_found();      //Imprimeix la pàgina 404
void handle_dispenser();      //Canvia l'estat del dispensador (True/False)
void handle_start();          //Iniciar proces de la comanda 
void dispenser_setup();       //Llista de dispensadors (noms toppins)
void server_setup();          //Inicialitzar servidor 


//FUNCIONS MOTORS//
void girCremalleraAnada(){           //moviment del segon motor
  digitalWrite(ENABLE2, LOW);        //activem el motor 2
  digitalWrite(DIR2, LOW);           //gir en el sentit horari
  for(int i = 0; i < 130; i++){      //dist=130
    digitalWrite(STEP2, HIGH);       //nivell alt
    delay(1);                        //velocitat de gir
    digitalWrite(STEP2, LOW);        //nivell baix
    delay(1);
  }
  delay(1000);                       //esperem que caiguin els toppings
  digitalWrite(ENABLE2, HIGH);       //desctivem el motor 2
}

void girCremalleraTornada(){         //moviment del segon motor 
  digitalWrite(ENABLE2, LOW);        //activem el motor 2
  digitalWrite(DIR2, HIGH);          //gir en el sentit antihorari
  digitalWrite(STEP2, HIGH);         //nivell alt
  delay(1);                          //velocitat de gir
  digitalWrite(STEP2, LOW);          //nivell baix
  delay(1);
  digitalWrite(ENABLE2, HIGH);       //desactivem el motor 2
}

void girHorari(){                  //moviment del stepper motor principal
  digitalWrite(ENABLE, LOW);       //activem el motor 1
  digitalWrite(DIR, LOW);          //gir en el sentit horari
  digitalWrite(STEP, HIGH);        //nivell alt
  delay(1);                        //velocitat de gir
  digitalWrite(STEP, LOW);         //nivell baix
  delay(1);
  digitalWrite(ENABLE, HIGH);      //desactivem el motor 1
}

void girAntihorari(int dist){       //moviment del stepper motor principal
  digitalWrite(ENABLE, LOW);        //activem el motor 1
  digitalWrite(DIR, HIGH);          //gir en el sentit antihorari
  for(int i = 0; i < dist; i++){ 
    digitalWrite(STEP, HIGH);       //nivell alt
    delay(1);                       //velocitat de gir
    digitalWrite(STEP, LOW);        //nivell baix
    delay(1);
  }
  digitalWrite(ENABLE, HIGH);       //desactivem el motor 1
}

//MAIN//
void setup() 
{
  //ESTABLIR ENTRADES I SORTIDES//
    pinMode(ENDSTOP1, INPUT); //pin 33 com a entrada
    pinMode(ENDSTOP2, INPUT); //pin 32 com a entrada
    pinMode(ENDSTOP3, INPUT); //pin 35 com a entrada
    pinMode(PUMP, OUTPUT);    //pin 15 com a sortida
    pinMode(STEP, OUTPUT);    //pin 12 com a sortida
    pinMode(DIR, OUTPUT);     //pin 14 com a sortida
    pinMode(STEP2, OUTPUT);   //pin 27 com a sortida
    pinMode(DIR2, OUTPUT);    //pin 26 com a sortida
    pinMode(ENABLE2, OUTPUT); //pin 25 com a sortida
    pinMode(ENABLE, OUTPUT);  //pin 13 com a sortida

  //INICIALITZACIÓ DEL SERVIDOR-ESP32//
    Serial.begin(115200);
    delay(2000);

    Serial.println("[Info] iniciando setup...");
    running = false;

    Serial.println("[Info] iniciando setup dispensers...");
    dispenser_setup();

    Serial.println("[Info] iniciando setup server...");
    server_setup(); 
}

void loop() {
    if (digitalRead(ENDSTOP3)==LOW){        //mentre la cremallera no estigui a la seva posició
      while (digitalRead(ENDSTOP3)==LOW) {
        girCremalleraTornada();
      }
    }
    if (digitalRead(ENDSTOP1)==LOW){        //mentre el transportador no estigui a la seva posició
      while (digitalRead(ENDSTOP1)==LOW) {
        girHorari();
      }
    }
    
    server.handleClient();    
    delay(2);//allow the cpu to switch to other tasks         
    if (running) 
    {
        int cur_distance = 0;
        int pos_final = 0;
        
        //PREPARACIO DEL IOGURT//  
        girAntihorari(YOGURT_DISTANCE);  //el transportador va cap al yogurt
        digitalWrite(PUMP, HIGH);        //activacio de la bomba
        delay(10000);                    //temps per omplir el iogurt 10 seg
        digitalWrite(PUMP, LOW);         //desctivacio de la bomba
        delay(500);                      //espera per a que acabi de caure el iogurt
        cur_distance=YOGURT_DISTANCE;    //actualitzar la distància del transportador 

        if (dispensadores[1].selected==1){      //si s'ha seleccionat el topping 1
          pos_final=DISPENSER_A-cur_distance;   //calculem la distancia entre la posicio actual i a la que volem anar
          girAntihorari(pos_final);             //moven el transportador a la posicio del diposit
          delay(200);                           //temps de seguretat de posicio
          girCremalleraAnada();                 //moven la cremallera per obrir el diposit
          delay(200);                           //temps de seguretat de posicio
          if (digitalRead(ENDSTOP3)==LOW){      //mentre la cremallera no estigui a la seva posició
            while (digitalRead(ENDSTOP3)==LOW) {
              girCremalleraTornada();
            }
          }
          cur_distance=DISPENSER_A;             //actualitzar la distància del transportador 
        }
        if (dispensadores[2].selected==1){      //si s'ha seleccionat el topping 2
          pos_final=DISPENSER_B-cur_distance;   //calculem la distancia entre la posicio actual i a la que volem anar
          girAntihorari(pos_final);             //moven el transportador a la posicio del diposit
          delay(200);                           //temps de seguretat de posicio
          girCremalleraAnada();                 //moven la cremallera per obrir el diposit
          delay(200);                           //temps de seguretat de posicio
          if (digitalRead(ENDSTOP3)==LOW){      //mentre la cremallera no estigui a la seva posició
            while (digitalRead(ENDSTOP3)==LOW) {
              girCremalleraTornada();
            }
          }
          cur_distance=DISPENSER_B;             //actualitzar la distància del transportador
        }
        if (dispensadores[3].selected==1){      //si s'ha seleccionat el topping 3
          pos_final=DISPENSER_C-cur_distance;   //calculem la distancia entre la posicio actual i a la que volem anar
          girAntihorari(pos_final);             //moven el transportador a la posicio del diposit
          delay(200);                           //temps de seguretat de posicio
          girCremalleraAnada();                 //moven la cremallera per obrir el diposit
          delay(200);                           //temps de seguretat de posicio
          if (digitalRead(ENDSTOP3)==LOW){      //mentre la cremallera no estigui a la seva posició
            while (digitalRead(ENDSTOP3)==LOW) {
              girCremalleraTornada();
            }
          }
          cur_distance=DISPENSER_C;             //actualitzar la distància del transportador
        }
        if (dispensadores[4].selected==1){      //si s'ha seleccionat el topping 4
          pos_final=DISPENSER_D-cur_distance;   //calculem la distancia entre la posicio actual i a la que volem anar
          girAntihorari(pos_final);             //moven el transportador a la posicio del diposit
          delay(200);                           //temps de seguretat de posicio
          girCremalleraAnada();                 //moven la cremallera per obrir el diposit
          delay(200);                           //temps de seguretat de posicio
          if (digitalRead(ENDSTOP3)==LOW){      //mentre la cremallera no estigui a la seva posició
            while (digitalRead(ENDSTOP3)==LOW) {
              girCremalleraTornada();
            }
          }
          cur_distance=DISPENSER_D;             //actualitzar la distància del transportador
        }
        if (dispensadores[5].selected==1){      //si s'ha seleccionat el topping 5
          pos_final=DISPENSER_E-cur_distance;   //calculem la distancia entre la posicio actual i a la que volem anar
          girAntihorari(pos_final);             //moven el transportador a la posicio del diposit
          delay(200);                           //temps de seguretat de posicio
          girCremalleraAnada();                 //moven la cremallera per obrir el diposit
          delay(200);                           //temps de seguretat de posicio
          if (digitalRead(ENDSTOP3)==LOW){      //mentre la cremallera no estigui a la seva posició
            while (digitalRead(ENDSTOP3)==LOW) {
              girCremalleraTornada();
            }
          }
          cur_distance=DISPENSER_E;             //actualitzar la distància del transportador
        }
        if (dispensadores[6].selected==1){      //si s'ha seleccionat el topping 6
          if (digitalRead(ENDSTOP2)==LOW){      //mentre la cremallera no estigui a la seva posició
            while (digitalRead(ENDSTOP2)==LOW) {
              girAntihorari(1);                 //moven el transportador a la posicio del diposit
            }
          }
          delay(200);                           //temps de seguretat de posicio
          girCremalleraAnada();                 //moven la cremallera per obrir el diposit
          delay(200);                           //temps de seguretat de posicio
          if (digitalRead(ENDSTOP3)==LOW){      //mentre la cremallera no estigui a la seva posició
            while (digitalRead(ENDSTOP3)==LOW) {
              girCremalleraTornada();
            }
          }
        }
        if (digitalRead(ENDSTOP1)==LOW){        //mentre el transportador no estigui a la seva posició
          while (digitalRead(ENDSTOP1)==LOW) {
            girHorari();
          }
        }
        //desactiven la selecció dels toppings previs//
        dispensadores[1].selected=false;
        dispensadores[2].selected=false;
        dispensadores[3].selected=false;
        dispensadores[4].selected=false;
        dispensadores[5].selected=false;
        dispensadores[6].selected=false;
        running = false;
    }
}

//FUNCIONS DEL SERVER//
void handle_root() 
{
    Serial.println("[Server] Handling root...");
    //DISSENY DE LA PAGINA AMB CODI HTML//
    /*<html>
        <head>
        </head>
        <body>
          <table> 
            <tr>  fila
              <td>  columna
                <...>  dispensadors
              </td>
            </tr>
          </table>
            <...>  botó start
         </body>
       </html>
    */


    String page = "<html><head></head><body><table>";   
    int mod;
    for (int i = 1; i < DISPENSER_COUNT; i++)
    {
        mod = i % 2;
        page += (mod == 1) ? "<tr>" : "";
        page += "<td>";
        page += "<input id=""";
        page += i;
        //al executar l'event onclick es redirecciona a la pagina /dispenser?id=[i]
        page += """ type=""checkbox"" onclick=""javascript:location.href='dispenser?id=";  
        page += i;
        page += "'"" ";
        page += (dispensadores[i].selected) ? "checked" : "";
        page += "><label for=""";
        page += i;
        page += """>";
        page += dispensadores[i].description;
        page += "</label>";
        page += "</td>";
        page += (mod == 0) ? "</tr>" : "";
    }

    page += "<tr><td>";
    //al executar l'event onclick es redirecciona a la pagina /start
    page += "<input type=""button"" value=""Preparar"" onclick=""javascript:location.href='start'"">";
    page += "</td></tr>";

    page += "</table></body></html>";

    server.send(200, "text/html", page); //enviar la pagina al client
}


//Imprimeix la pàgina 404//
void handle_not_found() 
{
    Serial.println("[Server] Handling not found...");
    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);
}

void handle_dispenser()
{
    int id = server.arg("id").toInt();  //obtenir el paràmetre id de la URL
    String message = "[Server] Handling " + dispensadores[id].description + "...";
    Serial.println(message);
    //en cas que no s'estigui executant poder modificar l'estat de la selecció del topping
    if (!running){
        dispensadores[id].selected = !dispensadores[id].selected;
    }
    handle_root(); //imprimir la pàgina de nou 
}

void handle_start()
{
    Serial.println("[Server] Handling start..."); 
    running = true;  //un cop s'ha clicat el preparar, comença el proces
    handle_root(); //imprimir la pàgina de nou 
}

void dispenser_setup()
{
    //inicialitza les variables dels toppings en false
    dispensadores[0].selected = false;  
    dispensadores[1].selected = false;  
    dispensadores[2].selected = false;
    dispensadores[3].selected = false;
    dispensadores[4].selected = false;
    dispensadores[5].selected = false;
    dispensadores[6].selected = false;
    
    //definim el nom dels toppings 
    dispensadores[0].description = "Dispensador Yogurt";
    dispensadores[1].description = "Filipinos blanc";
    dispensadores[2].description = "Filipinos negres";
    dispensadores[3].description = "Chips Ahoy";
    dispensadores[4].description = "Cereals";
    dispensadores[5].description = "Galetes de xocolata";
    dispensadores[6].description = "Lacasitos";
}

void server_setup() {
    WiFi.mode(WIFI_STA);  //estableix el mode WIFI a mode WIFI STA
    WiFi.begin(ssid, password);  //inicia la connexió 
    Serial.println("[Info] Waiting for wifi connection...");
    
    //espera a la connexio
    while (WiFi.status() != WL_CONNECTED){
      delay(500);
      Serial.print(".");
    }

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

    if (MDNS.begin("esp32")) {
        Serial.println("[Info] MDNS responder started");
    }
    //deineix la funció per encarregar-se del callback del cliente
    //i s'executa el handle_root quan s'accedeix a la pagina principal
    server.on("/", handle_root);  
    
    //deineix la funció per encarregar-se del callback del cliente
    //i s'executa el handle_dispenser quan s'accedeix a la pagina /dispenser
    server.on("/dispenser", HTTP_GET, handle_dispenser);
    
    //deineix la funció per encarregar-se del callback del cliente
    //i s'executa el handle_start quan s'accedeix a la pagina /start
    server.on("/start", HTTP_GET, handle_start);
    
    //deineix la funció per encarregar-se del callback del cliente
    //i s'executa el handle_not_found quan no es troba la pagina
    server.onNotFound(handle_not_found);

    server.begin(); //inicia al servidor 
    Serial.println("[Info] HTTP server started");
}
 

DESIGN
11062b_f0cd2b56e86443d68d21b6bc12fe055c_

DESIGN

The designing part has helped the project to create different pieces using a 3D printer.

All the pieces have been created using SolidWorks.

There is a link in case someone needs to download some part. 

estructuraCompleta.png

Assembled structure

Engine mount

suporteEje.png

Second engine mount 

soporteCremallera.png

Gear

engranatge.png

Transporter

transportador.png

Axle support

suporteEje.png

Dispenser bracket

soporteDispensador.png

Rack

cremallera.png

Structure

estructura.png
CALCULATIONS

Calculations

An automatic yogurt delivery process has been created using dispensers for keeping the toppings and a moveable platform using an stepper motor, all controlled by a system programmed by Arduino.

Engine strength to
move the transport

On the yogurt conveyor belt we consider the weight of the yogurt structure (2.1kg empty). Assuming almost zero friction force on the toothed belt, since the diameter ratio of the driven wheel and the driving wheel are equal, we can say that the weight with a glass full of cereals is 2.1kg for the structure plus 300 grams for the glass. This means a total weight of 2.4kg without counting the friction of the belt or the chrome axes. If, according to the manufacturer, our motor has a force of 3.2kg cm, you will have no problem moving the yogurt transport cart. We can theoretically consider that we will have a margin of 25% compared to the 100% that our engine can make have force.

motorStrenghtToMoveTheTransport_page-0001.jpg

Engine strength to
push the botton

To find out if our stepper motor could handle the spring force of our dispensers, we checked the force exerted by the spring. To do this, we first calculate the clean weight of the entire dispenser without refilling it, in order to check if the same weight of the dispenser could equal the force of the spring. Then we put all the support of the dispenser on the button, placing the tank vertically. Knowing that the empty tank weighed 700 grams and that the button reduced its travel by approximately 50%, we knew that if we used 1400 grams we lost the button almost 100%. We also know that the spring has a greater resistance to force the more compressed it is, so we put a 50% increase in force to ensure the safety of the motor's force. That means that if our 100% was approximately 1400 grams and we raise it to 150% we can say that the weight increases to 2100 grams. We still wanted to consider the resistance of the food inside the tank that with the weight will make more pressure to cancel the force of the spring and we increased it by 40% more that left us with a weight of 2940 grams. According to the motor manufacturer, our nema 17 motor is designed to push a weight of 3.2 kg cm without having any kind of difficulty, so our 2940 grams if we convert them to kg we have 2.94 kg, that puts a margin of 8 % compared to 3.2kg, which is 100%.

 
diposit_page-0001.jpg

Time needed to fill a yogurt terrine

In our homos reasoned yogurt filling pump as if we were filling the quantities of a glass of water (200 milliliters). If we know that our water pump is capable of moving 400 milliliters per minute and we know that our tub will have to fill about 150 milliliters, we are talking about 37.5% with respect to the maximum of our pump. But we also have to count the density of the yogurt with respect to that of the water. Approximately we will put that the yogurt is 50% denser so if we need 50% more, we have 300 milliliters. If our pump can push 400 milliliters per minute we can say that our tub can be filled in one minute and with a margin of 25% of the total.

TIMEFILLYOGURT_page-0001 (1).jpg

Budget

budet1.PNG
budget2.PNG
work_hours.PNG
BUGDET
bottom of page