This is an old revision of the document!
This Arduino-based system offers a convenient and customizable way to make delicious cocoa milk drinks at home. The project contains a smart liquid dispenser with a keyboard that allows the user to input the desired quantity of milk and a distance sensor that detects the presence of a cup to activate the powder dispensing mechanism.
The idea behind this project was to simplify and automate the process of making these drinks and ensure consistent quality every time. MilkyWay Maker is a project that was born out of a love for milk-coffee beverages, such as Nescafe and Nesquik thus I strongly believe that this project is not only useful for coffee lovers, but also for those interested in learning about electronics and programming.
The user can input the desired quantity of milk using the keyboard, and the system dispenses the milk accordingly. The distance sensor detects the presence of a cup, activating the powder dispensing mechanism to add the cocoa powder. This ensures that the drink is prepared accurately and consistently every time.
The project is not only aimed at coffee lovers but also at those interested in learning about electronics and programming. It combines the functionality of Arduino, the convenience of automation, and the enjoyment of delicious milk-coffee beverages. The provided code includes various functionalities, such as reading the keypad inputs, controlling the servo motor, calculating the flow rate of milk, displaying information on an LCD screen, and playing beep sequences for notifications.
Here are the features of this setup:
Below is the overall flow and explanation of the code. It combines input from the keypad, measurements from the ultrasonic sensor and flow sensor, and control of the water pump and servo motor to automate the process of dispensing milk based on the desired volume.
#include <Keypad.h> #include <LiquidCrystal_I2C.h> #include <Servo.h> const int ROW_NUM = 4; //4 rows const int COLUMN_NUM = 3; //3 columns const int thresholdDistance = 5; // in cm char keys[ROW_NUM][COLUMN_NUM] = { {'1','2','3'}, {'4','5','6'}, {'7','8','9'}, {'*','0','#'} }; LiquidCrystal_I2C lcd(0x27, 16, 2); byte pin_rows[ROW_NUM] = {4, 5, 6, 7}; //connect to the row pinouts of the keypad byte pin_column[COLUMN_NUM] = {8,9, 10}; //connect to the column pinouts of the keypad Keypad keypad = Keypad(makeKeymap(keys), pin_rows, pin_column, ROW_NUM, COLUMN_NUM);
const int ultrasonicTrigPin = 12; const int ultrasonicEchoPin = 13; const int servoPin = 11; int sensorInterrupt = 0; // interrupt 0 int sensorPin = 2; //Digital Pin 2 unsigned int SetPoint = 400; //400 milileter int buzzerPin = A3; String code=""; /*The hall-effect flow sensor outputs pulses per second per litre/minute of flow.*/ float calibrationFactor = 40; //You can change according to your datasheet volatile byte pulseCount =0; float flowRate = 0.0; unsigned int flowMilliLitres =0; unsigned long totalMilliLitres = 0, volume = 0; unsigned long oldTime; const int relais_moteur = 3; // the relay is connected to pin 3 of the Arduino board Servo myServo; bool objectDetected = false; bool volumeEntered = false; bool beepSequence = false; bool beepSequence2 = false;
setup()
function to initialize the settings and configurations of the Arduino board and connected components. Set pin modes, attach interrupts, and initialize the LCD display.<code> void setup() {
totalMilliLitres = 0; pinMode(relais_moteur, OUTPUT); pinMode(buzzerPin, OUTPUT); lcd.init(); // display initialization lcd.clear(); lcd.backlight(); // activate the backlight lcd.setCursor(0, 0); // stand in the front line lcd.print("Insert volume:"); Serial.begin(9600); myServo.attach(servoPin); myServo.write(70); pinMode(ultrasonicTrigPin, OUTPUT); pinMode(ultrasonicEchoPin, INPUT); delay(2000); pinMode(sensorPin, INPUT); digitalWrite(sensorPin, HIGH); attachInterrupt(sensorInterrupt, pulseCounter, FALLING); //you can use Rising or Falling
}
<\code>
loop()
function, which is the main execution loop of the program. It continuously checks for inputs from the keypad and performs actions based on the input. It also checks for the presence of a cup using the ultrasonic sensor and controls the servo motor accordingly.loop()
function and accumulate them to form a volume value. Display the entered volume on the LCD display.<code> void loop() {
if (!objectDetected) { // Read distance from ultrasonic sensor delay(2000); int distance = getDistance(); // Check if distance is below threshold if (distance <= thresholdDistance) { // Move servo to 90 degrees delay(2000); myServo.write(120); delay(3000); // Reset servo to initial position myServo.write(70); // Set object detected flag to true objectDetected = true; } }
// Wait for a moment before checking again delay(100);
///////////////////////////////////////// char key = keypad.getKey();
if (key) { A key on the keyboard is pressed code += key; lcd.setCursor(0, 1); stand on the second line
lcd.print(code); // show volume value delay(100); }
if (key == '#') { if you press the 'D' key if (code.toInt() ⇐ 1000) { volume = code.toInt(); volumeEntered = true; if(!beepSequence) { playBeepSequence(buzzerPin); beepSequence = true; } } else { lcd.clear(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print(“Insert volume:”); } code = ””; } if (key == '*') { if you press the '*' key
resetProcess(); volumeEntered = false; return; }
if(volumeEntered){ if (totalMilliLitres < volume ) { digitalWrite(relais_moteur, HIGH); // Start the water pump
if 1)
detachInterrupt(sensorInterrupt); flowRate = ((1000.0 / (millis() - oldTime)) * pulseCount) / calibrationFactor;
oldTime = millis(); flowMilliLitres = (flowRate / 60) * 1000;
totalMilliLitres += flowMilliLitres;
unsigned int frac;
Serial.print("Flow rate: "); Serial.print(flowMilliLitres, DEC); // Print the integer part of the variable Serial.print("mL/Second"); Serial.print("\t"); lcd.clear(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print("debit:"); lcd.print(flowMilliLitres); // Show the flow rate on the lcd display lcd.print(" ml/s"); // Print the cumulative total of litres flowed since starting Serial.print("Output Liquid Quantity: "); Serial.print(totalMilliLitres,DEC);
Serial.println("mL"); Serial.print("\t"); lcd.setCursor(0, 1); lcd.print("volume:"); lcd.print(totalMilliLitres); // Show quantity filled pulseCount = 0;
attachInterrupt(sensorInterrupt, pulseCounter, FALLING); }}else {
digitalWrite(relais_moteur, LOW); // Stop the water pump volume=0; volumeEntered = false; if(!beepSequence2) { playBeepSequence(buzzerPin); beepSequence2 = true; } lcd.setCursor(0, 0); lcd.print("Volume reached!"); lcd.setCursor(0, 1); lcd.print("Remove cup!"); delay(10000); ////int distance2 = getDistance(); //if(distance2 > thresholdDistance) //{ objectDetected = false; //} delay(10000); resetProcess(); return; }} } <\code>
pulseCounter()
function, which is an interrupt service routine that increments a pulse count variable. It is triggered by the falling edge of the pulse from the flow sensor, indicating the flow of liquid.resetProcess()
function to reset the variables and settings related to the flow measurement process. Clear the LCD display, set it to the initial state, and initialize the necessary variables.getDistance()
function to measure the distance using the ultrasonic sensor. Send a trigger pulse and calculate the duration of the echo pulse to determine the distance. Return the distance value in centimeters.playBeepSequence()
function to generate a beep sound using a buzzer. Play a sequence of three short beeps with a delay between them.