Introduction: Beer Pouring Robot
Pouring beer is an art, and definitely part of the overall tasting experience and that too particularly, when you require adequate foam perfectly lined up along the edge of your glass. Since Belgium is the land of beers, we decided to make a pouring robot for two famous Belgian beers (Duvel and Jupiler) and made a robot as per their glass and bottle sizes as a part of our Master One project. The idea was to make a common robot for both Beer brands. Based on the availability, we built robot using following components & materials.
- Hardware: Arduino microcontroller.
- Material for the structure: MDF, Plexiglass,
- 3d printing: PLA
- Modelling Software: Solidworks / Inventor
- Coding: Fritzing and Arduino
Step 1: Required Components
This step is all about our choices and availability. May differ in other conditions.
1) DC Motor (2 pcs) [link]
2) Radial Ball Bearing (2 pcs) [Link]
3) Shaft Coupler (2 pcs) [Link]
4) LCD Screen (1 pcs) [Link]
5) Load cell (1 pcs) [Link]
6) Arduino Uno (1 pcs) [Link]
7) Sensor Module for the load cell (Amplifier HX711) ( 1 pcs) [Link]
8) H Bridge Motor Driver (1 pcs) [Link]
9) Potentiometer ( 2 pcs)
10) MDF (Hardware Stores)
11) Plexiglass (Hardware Stores)
12) PLA roll for 3d printing*
Total ~ 200 €
*If a 3D printer is not available, most of the printed pieces can be anyway ordered or machined.
Step 2: Holder Design
Select beer brands with which you need to make make a robot. (After a brief of discussion, we decided to go with a mild yet very famous beer Jupiler and a Strong beer Duvel). Brainstorming on the different ideas to design a suitable holder for both glass and bottle in a common setup. (We followed the scrum method to come up with some innovative designs and finalized with two following designs)
Bottle Holder:
Designed the two bottles in the Solidworks and comparing both, by superimposing one or other.
Outlining boundaries of the combined model, a shell was prepared with a bit of offset to them. A gripper needs to be designed to hold the bottle (Our gripper was embedded inside the holder design by protruding three tentacles out of the plane.)
Making a suitable connector for the holder to connect it with the actuators/motors (In our case we merged connector and holder together since we were to 3d print both with PLA)
If designing connector and holder separately, it’s better to go with two different materials (preferably metal – polymer combination)
Glass Holder:
A similar approach has followed the glasses of Duvel and Jupiler. A glass may not require a complicated 3d printed shell, but needs something to block its unwanted vertical motion. However, like the one in bottle holder unit, the glass holder will also require a connector to connect to the actuator. (our idea of Glass holder was just an L support for resting the glass with a section between of a suitable diameter common to both. However, since we also needed to design a connector we decided to make it 3d printed while machining rest of holder assembly out of plexiglass)
Note: In general, it can be done with any two different bottles and glasses of alcoholic or non-alcoholic beverages.
Step 3: Frame Design & Component / Material Selection
Frame Design
Once Holder designs are finalized for both bottle and Glass, proceed with Frame design. Prepare a rough sketch sketched our rough idea of automated beer pouring
Now an important factor in automated pouring is, fixing the axis correctly and determining the exact distance between the bottle and glass holder axis so that the beer is poured exactly in the glass. But then for that, one needs at least one axis to placed and then iterate with the position of another axis by trial and error. (In our case we fixed the axis of the bottle and design a frame of the assembly around it. One can even fix the glass axis and work around it).
It's better to design frame into two sections. This will help in easier assembly. Also having different materials for each section becomes an advantage in terms of strength without compromising aesthetics.(We had Upper half was made of Plexiglass, to make the view of beer pouring better while lower section was made of MDF. The Upper half was a primary support to all the structural components for the beer pouring. The lower half is primarily a support for the upper half. It also houses electrical and electronic components and has a battery compartment provision. Connection between Upper and Lower half is made using 2 hinges, in this way if needed we can easily have access to the electronics. )
Component selection
To Pour the beer perfectly, a controlled motion of both glass and the bottle at the same time. This can be done by rotating them with the help of shaft connected to the motor. The motor selection needs to be finalized with the by calculating effective torque required to rotate both glass and the bottle. A Rotary Encoder/ potentiometer is needed in the Close loop with DC to control the angle in case if it's not servo motor. (We used DC motor of 25 kg*cm torque was finalized for both Axis coupled with a Potentiometer in Closed loop)
Along with the motor and shaft, Shaft couplers, bearings and their mounts, good support for the motor, motor clamp are also required to designed/procured. ( We procured bearings & shaft couplers and 3d printed the bearing holder, its base support, motor clamps and motor supports, potentiometer holder, and potentiometer support)
Step 4: Circuit Design and Sensor Selection
The electrical parts needed in this project are:
- 1x Arduino UNO board;
- 2x DC Motor (High torque); [Motors]
- 1x H-Bridge (L298N); [H-Bridge]
- 1x Adapter AC/DC 220V AC-12V DC;
- 1x Load cell (0-10kg); [Load cell]
- 1x Load cell amplifier HX711; [Load cell amplifier]
- 2x Potentiometer 10kΩ;
- 1x LCD Display (16x2);
- 1x Inter-Integrated Circuit Protocol I2C;
- 1x Push Button (Start button);
- 1x ON/OFF Button;
- Wires in different colours.
The torques required to rotate the glass and the bottle are very high, so chose two motors with high torque and low speed as we need to pour the beer slowly. In order to control the speed and direction of the motors, an H-Bridge is connected to the motors and Arduino. The speed is controlled by the PWM pins of Arduino (using the function “analog write(pin,speedValue)”). Since the motor voltage supply is 12V we use an AC/DC adapter that permits to transform the voltage from 220V AC to 12V DC.
(We used the potentiometers to perform a closed-loop for the motors. In fact, with proper computing, they give the angular position of the motor shafts.)
To identify the different possible combinations of the bottles and the glasses a load cell is used equipped with an amplifier (HX711) capable of detect small weight variations. Each weight corresponds to one combination. Depending upon the desired output the number of cases may vary.( In total, we considered 6 combinations as we have 2 different bottles and 2 different glasses. The sixth combination denotes the cases different from the others. All the cases are shown later in the benchmarking.)
The load cell is a component that allows measuring the weight of an object. To use it, a sensor module is necessary (Amplifier HX711) in order to connect the load cell to the Arduino. A library will be necessary to use the HX711. It will be placed on one side of the plexiglass frame, allowing it to measure the weight of both the bottle and the glass at the same time. During the setup part of the program, the reading on the load cell will be reset, and this state will be considered as the zero weight. When a bottle and/or a glass are placed on the machine, the load cell will measure the total weight, which will be identified as a combination of bottle and glass (e.g.: between 440g and 510g: Duvel glass and Duvel bottle, and between 5g and 35g: only a Jupiler glass). Depending on the case, the LCD will ask the user to place a matching combination of glass and bottle (in case of a wrong combination) or to press the start button to launch the pouring program associated with the detected case (Jupiler pouring routine for Jupiler glass and bottle and Duvel pouring routine for Duvel glass and bottle).See the image for reference
These weight thresholds might be different for each build of the machine and have to be adjusted by reading the output of the load cell for each relevant weight case.
The LCD, equipped with the protocol I2C to save pins and make the communication easy, shows all the instructions to be followed during all the process.
At the end, the robot is provided with one push button that permits to start the cycle.
Attachments
Step 5: Coding
In order to make the code clear, we divided it in different parts.
First part: Inclusion of libraries. This part is important to streamline the code. In fact, including some libraries, we can use existing functions instead of creating them by ourselves. We used the libraries for the load cell and for the LCD as shown in the picture below:
Second part: Declaration of global variables. These variables are used in all the code.
Third part: Pin declaration. Here we are setting properly the pin connected on Arduino.
Fourth part: This part is dedicated to the creation of the functions
- Measure weight. It can measure the weight placed on the load cell. We use this function every time we need to know which bottle and glass are fixed in the boxes.
- Reading angle. It permits to realize the closed loop for the motors. Indeed, reading and analysing the value given by the potentiometers, we can know the angular position of the bottle and the glass.
- Movement. This contains two functions that perform the rotation of the motors. One for the glass and the other one for the bottle.
- Reset. It is used at the end of the cycle to reset the program and make it able for a new pour.
Fifth part. The last part is dedicated for the main code. Here, there are the setup and the loop.
- Void setup. This part is performed just one time, at the beginning of the program. We used the setup to initialize all the pin (INPUT or OUTPUT) and the LCD.
- Void loop. This part is the real program and it is performed infinitive times in loop (until the power is supplied). Here we are using the function “switch-case” and, depending on the weight read by the load cell, the program does different actions. For example, it shows on the LCD the next step to do like “Insert Duvel bottle”. Then, if the case is compatible and the start button is pressed, the robot starts pouring. The pouring is divided in several cases in order to pour the beer properly.
At the end, if the process is terminated we can restart the program by pressing the start button and the loop begins again.
/////////////////////////////LIBRARIES/////////////////////// #include "HX711.h" #include <Wire.h> #include <LiquidCrystal_I2C.h> //////////////////////GLOBAL VARIABLES DECLARATION/////////////// float calibration_factor=205; //This calibration factor is adjusted according to the load cell int weightCase; //depending on the weight, the machine will behave differently int pouringState=0; //How far in pouring we are bool glassReady; //Used to determine if the glass reached the target angle bool bottleReady; //Used to determine if the bottle reached the target angle bool pouringDone=false; bool runProgram=false; ///////////////////////PIN DECLARATION///////////////////////// /*************Potentiometers**************/ #define potG A2 //Glass potentiometer #define potB A3 //Bottle potentiometer /****Load cell and HX711****/ #define LCDT A1 #define LCSCK A0 HX711 scale(LCDT,LCSCK); //HX711 allows for an easier read on the load cell data /*********Motors************/ //Glass motor #define dir1PinG 12 //Direction pin 1 of the glass motor #define dir2PinG 13 //Direction pin 2 of the glass motor #define speedPinG 10 //PWM pin in order to control the motor speed //Bottle motor #define dir1PinB 9 //Direction pin 1 of the bottle motor #define dir2PinB 8 //Direction pin 2 of the bottle motor #define speedPinB 10 //PWM pin in order to control the motor speed /*************LCD**************/ LiquidCrystal_I2C lcd(0x27, 16, 2); //Set the LCD address to 0x27 for a 16 chars and 2 line display /***********BUTTON**********/ #define buttonPin 2 /////////////////////////////CREATION OF FUNCTIONS///////////////////////// /*********Measure weight************/ int funcWeightCase (){ //determines the behaviour of the machine based on the measured weight float weight; weight=scale.get_units(), 10; if(weight<0){ weight=0.00; } if(weight<5){ weightCase=1; } else if(weight>5 && weight<<35){ //Jupiler glass weightCase=2; } else if(weight>330 && weight<400){ //Jupiler bottle and glass weightCase=5; } else if(weight>35 && weight<70){ //Duvel glass weightCase=3; } else if(weight>400 && weight<510){ //Duvel bottle and glass weightCase=4; } else { //other cases weightCase=6; } return weightCase; } /*********Reading angle************/ float readAngleGlass() { //Returns the angle of the glass in degrees, from the vertical resting position float raw=analogRead(potG); float angle=(raw-506)/2.84167; // The glass potentiometer returns a value ranging from 0 to 1023 for one revolution. We set up the potentiometer so that its resting position returns 506. return angle; } /************Movement*************/ bool moveBottle(int angle, int rSpeed){ //returns true if the bottle is tilted at the right angle bool posReached=false; int diff=angle-(int)readAngleBottle(); //Difference between target angle and current angle. if(diff>3){ digitalWrite(dir1PinB,HIGH); //HIGH => clockwise digitalWrite(dir2PinB,LOW); analogWrite(speedPinB,rSpeed); } else if(diff<-3){ digitalWrite(dir1PinB,LOW); //LOW => counter-clockwise digitalWrite(dir2PinB,HIGH); analogWrite(speedPinB,rSpeed); } else if(abs(diff)<=3){ //If the current angle is less than 3 degrees away from target angle, it's ok digitalWrite(dir1PinB, LOW); digitalWrite(dir2PinB, LOW); analogWrite(speedPinB,0); posReached=true; } return posReached; } bool moveGlass(int angle, int rSpeed){ //returns true if the bottle is tilted at the right angle bool posReached=false; int diff=angle-(int)readAngleGlass(); //Difference between target angle and current angle id(diff>3){ digitalWrite(dir1PinG,HIGH); //HIGH => counter-clockwise digitalWrite(dir2PinG,LOW); analogWrite(speedPinG,rSpeed); } else if(diff<-3){ digitalWrite(dir1PinG,LOW); //LOW => clockwise digitalWrite(dir2PinG,HIGH); analogWrite(speedPinG,rSpeed); } else if(abs(diff)<=3){ //If the current angle is less than 3 degrees away from target angle, it's ok digitalWrite(dir1PinG,LOW); digitalWrite(dir2PinG,LOW); analogWrite(speedPinG,0); posReached=true; } return posReached; } /***********Reset************/ void(* resetFunc) (void) =0; //Used to reset the program at the end, if needed ///////////////////////////////////MAIN CODE///////////////////////////////// void setup(){ pinMode(dir1PinG,OUTPUT); pinMode(dir2PinG,OUTPUT); pinMode(speedPinG,OUTPUT); pinMode(dir1PinB,OUTPUT); pinMode(dir2PinB,OUTPUT); pinMode(speedPinB,OUTPUT); pinMode(potG,INPUT); pinMode(potB,INPUT); pinMode(buttonPin,INPUT); scale.set_scale(calibration_factor); //Calibration of the scale scale.tare(); //The base weight of the machine is not important lcd.begin(); //Setup the LCD lcd.backlight(); lcd.clear(); lcd.setCursor(0,0); delay(2000); lcd.print("Welcome to the"); lcd.setcursor(0,1); lcd.print("beer robot!"); delay(1500); lcd.clear(); lcd.setCursor(0,0); lcd.print("Enter compatible"); lcd.setCursor(0,1); lcd.print("bottle and glass"); delay(2000); lcd.clear(); } void loop(){ weightCase=funcWeightCase(); //Analyse the weight and deduce the bottle and glass in the machine if(!runProgram){ switch (weightCase){ case 1: lcd.setCursor(0,0); lcd.print("Enter compatible"); lcd.setCursor(0,1); lcd.prin("bottle and glass"); break; case 2: lcd.setCursor(0,0); lcd.print("Insert Jupiler"); lcd.setCursor(0,1); lcd.print("bottle"); break; case 5: lcd.setCursor(0,0); lcd.print("Press start to"); lcd.setCursor(0,1); lcd.print("pour the Jupiler!"); if(digitalRead(buttonPin)){ //Button pressed => 1 is written on the pin. Button not pressed => 0 on the pin runProgram=true; lcd.clear(); } break; case 6: lcd.setCursor(0,0); lcd.print("Bottle and glass"); lcd.setCursor(0,1); lcd.print("must match!"); break; case 3: lcd.setCursor(0,0); lcd.print("Insert Duvel"); lcd.setCursor(0,1); lcd.print("bottle!"); break; case 4: lcd.setCursor(0,0); lcd.print("Press start to"); lcd.setCursor(0,1); lcd.print("pour the Duvel"); if(digitalRead(buttonPin)){ //Button pressed => 1 is written on the pin. Button not pressed => 0 on the pin runProgram=true; lcd.clear(); } break; } } if(runProgram && weightCase==5 && !pouringDone){ //If jupiler glass and bottle detected and button pressed and pouring is not over, start the Jupiler pouring lcd.setCursor(0,0); lcd.print("Jupiler pouring"); lcd.setCursor(0,1); lcd.print("Wait please..."); switch(pouringState){ //How far we are in the pouring routine. The Jupiler routine is very slow to account for the sub-optimal positioning of the bottle and glass, to try not to spill case 0: bottleReady=moveBottle(0,27); glassReady=moveGlass(0,30); if(glassReady && bottleReady){ delay(1000); pouringState=2; } break; case 1: bottleReady=moveBottle(85,24); if(bottleReady){ delay(2000); pouringState=2; } break; case 2: bottleReady=moveBottle(87,24); if(bottleReady){ delay(3000); pouringState=3; } break; case 3: bottleReady=moveBottle(90,20); glassReady=moveGlass(7,20); if(bottleReady){ delay(3000); pouringState=4; } break; case 4: bottleReady=moveBottle(92,22); glassReady=moveGlass(4,30); if(bottleReady){ delay(2000); pouringState=5; } break; case 5: bottleReady=moveBottle(96,22); if(bottleReady){ delay(3000); pouringState=6; } break; case 6: bottleReady=moveBottle(98,22); if(bottleReady){ delay(4000); pouringState=7; } break; case 7: bottleReady=moveBottle(100,22); if(bottleReady){ delay(4000); pouringState=8; } break; case 8 bottleReady=moveBottle(103,22); if(bottleReady){ delay(4000); pouringState=9; } break; case 9: bottleReady=moveBottle(98,22); if(bottleReady){ delay(3000); pouringState=10; } break; case 10: bottleReady=moveBottle(100,22); if(bottleReady){ delay(3000); pouringState=11; } break; case 11: bottleReady=moveBottle(103,22); if(bottleReady){ delay(3000); pouringState=12; } break; case 12: bottleReady=moveBottle(107,22); if(bottleReady){ delay(3000); pouringState=13; } break; case 13: bottleReady=moveBottle(0,24); glassReady=moveGlass(2,27); if(glassReady && bottleReady){ pouringDone=true; lcd.clear(); } break; } } else if(runProgram && weightCase==4 && !pouringDone){ //If Duvel glass and bottle detected and button pressed and pouring is not over, start the Duvel pouring lcd.setCursor(0,0); lcd.print("Duvel pouring"); lcd.setCursor(0,1); lcd.print("Wait please..."); switch(pouringState){ case 0: bottleReady=moveBottle(30,27); glassReady=moveGlass(30,30); if(glassReady && bottleReady){ pouringState=1; } break; case 1: bottleReady=moveBottle(85,19); glassReady=moveGlass(45,30); if(bottleReady && glassReady){ delay(1000); pouringState=2; } break; case 2: bottleReady=moveBottle(90,19); glassReady=moveGlass(40,30); if(bottleReady && glassReady){ delay(1000); pouringState=3; } break; case 3: bottleReady=moveBottle(95,19); if(bottleReady){ delay(1000); pouringState=4; } break; case 4: bottleReady=moveBottle(100,19); //Max angle for the bottle = 100 glassReady=moveGlass(40,20); if(glassReady && bottleReady){ delay(10000); pouringState=5; } case 5: bottleReady=moveBottle(0,27); glassReady=moveGlass(5,27); if(glassReady && bottleReady){ pouringDone=true; lcd.clear(); } break; } } if(pouringDone){ lcd.setCursor(0,0); lcd.print("Press button to"); lcd.setCursor(0,1); lcd.print("restart"); if(digitalRead(buttonPin)){ //Button pressed => 1 is written on the pin. Button not pressed => 0 on the pin. resetFunc(); } } }<br>
Attachments
Step 6: Manufacturing & Assembly
Manufacturing for this project can mainly be categorized into two types
1) 3d Printing
For 3d printing care needs to be taken about shrinkage percentage for the selected material, the maximum dimensional capacity of the printer, Density of the material, the temperature of printing, the effective placing of support so that the desired component is erected properly in the building area.
(We did our printing in the Ultimaker 2.0 with PLA material. A guide to using this machine is given in https://www.instructables.com/id/Ultimaker-3-Guid...)
Parts which we 3d printed were as follows,
1) Bottle Holder (1 pcs).
2) Bearing Holder (2 pcs)
3) Potentiometer Holder (2 pcs)
4) Potentiometer Support (2pcs)
5) Motor Holder (4 pcs)
6) Motor Clamp (2 pcs)
7) Glass holder connector (1 pcs)
8) Potentiometer Shaft Coupler (2 pcs)
2) Machining:
This can either be done by laser cutting for material sheets or by the cutting machine in case of thicker sheets of wood or MDF. A bit of offset or mismatch during assembly can be solved by grinding or filing to reduce the diameter or smoothen the edge of the components.
(We did all our Plexiglass cutting by laser cutting while MDF was cut by cutter and holes were drilled by drilling machine).
Components did by laser cutting:
1) Glass Holder
2) Upper section of the Assembly Frame
Components did by Cutting machine:
1) Lower section of Assembly Frame
2) Motor mounts
Note: While manufacturing, it’s better to standardize the hole sizes for components which are assembled by bolting (we did it by M4 bolts and screws. The CAD are also attached of the whole assembly for reference).
Step 7: Bechmarking & Trails
This was a crucial stage since it consumes a lot of time in executing the designed codes on the robot. Start with one of the two brands, and once succeeded try to implement the same on the other brand. Note the changes in speed and angle of rotation for the other brand and try to change it relatively.
Also, It is preferred if you use following liquid cycles for testing for each brand just to be safe with electronics and physical setup
1) No liquid - 3 cycles
2) Water - 3 cycles
3) Beer - 2 cycles
In the table we can see the different cases between the different bottles and glasses. Just the cases 4 and 5 are good to pour the beer because the brand of both bottle and glass is compatible. The table shows also the weight range in which the cases are defined.
Step 8: Results
Once the tests with water gave good results, it was time to try it with real beer. Unfortunately, those results were not really up to mark. For the Jupiler, we couldn't get proper foam because of the glass & bottle position.
The main problem lies with the fact that the relative position/angle of the bottle to the glass was not suitable to correctly pour both beers as the glass and bottles had very different shapes.
Setted the relative position within the plexiglass frame, it made it impossible to adjust it afterwards. The lack of time did not permit us to adjust the position by trials and errors with real beer. Apart from this adjustment in the positions, the robot is perfectly working (beer recognition, control of the motors,...).
Once the position problem is solved, you have just to sit, make the robot do the hard job for you and enjoy a Belgian beer!