In week 15 and 17 we had the group assignment to design a machine that included mechanics, actuation and automation. This week the focus was on machine design. The machine should be actuated and automated.
Special thanks to my friend Jörg!!! Without him and his motivation all this would not have worked!
First, we reviewed all the requirements the board needed. We wanted to use a DOIT Esp32 DevKit v1 board for control. This should address the stepper motor, the pumps, the endstop, the servos and later the LEDs. To control the servos we use the Adafruit 16-Channel 12-bit PWM/Servo Driver - I2C interface - PCA9685 via I2C for PWM control, so our PWM driver. First we tried the whole thing out one by one and then we started with the design. At the beginning we developed the following:
During the first iteration different arrangements were changed. Like some components were on the wrong side.
For the second iteration we had to use the PINOUTS, which you can really use. Due to the first iteration there were design changes which were reflected in the PIN mapping. Next time we’ll pay a lot of attention to which PINs actually support which functions.
The final parts list was as follows:
Qty | Value | Device | Package | Parts | Description |
---|---|---|---|---|---|
1 | PINHD-1X3 | 1X03 | ENDSTOP | PIN HEADER | |
2 | PINHD-1X4 | 1X04 | I2C, STEPPERMOTOR | PIN HEADER | |
1 | PINHD-2X2 | 2X02 | POWER_+12V | PIN HEADER | |
2 | PINHD-2X3 | 2X03 | MS, RGB | PIN HEADER | |
1 | PINHD-2X4 | 2X04 | POWER_+5V | PIN HEADER | |
1 | PINHD-2X6 | 2X06 | EXTRA_IN | PIN HEADER | |
1 | PINHD-2X7 | 2X07 | PUMP | PIN HEADER | |
1 | 1.8K | R-EU_R1206 | R1206 | R10 | RESISTOR, European symbol |
2 | 100nF | C-EUC1206 | C1206 | C3, C9 | CAPACITOR, European symbol |
2 | 10K | R-EU_R1206 | R1206 | R1, R3 | RESISTOR, European symbol |
1 | 1K | R-EU_R1206 | R1206 | R6 | RESISTOR, European symbol |
1 | 1uF | C-EUC1206 | C1206 | C4 | CAPACITOR, European symbol |
1 | 200mA/40V | TRANS_PNP-PMBT3906|MMBT3906L | SOT23-3 | MBT2N3906 | PNP transistor |
1 | 3.3K | R-EU_R1206 | R1206 | R2 | RESISTOR, European symbol |
2 | 4.7K | R-EU_R1206 | R1206 | R7, R8 | RESISTOR, European symbol |
2 | 470uF | CPOL-EUE3.5-8 | E3,5-8 | C1, C2 | POLARIZED CAPACITOR, European symbol |
1 | DRV8825 | DRV8825 | DRV8825 | U$1 | |
1 | ESP32-DEVKIT1_NOHOLES | ESP32-DEVKIT1_NOHOLES | NODELUA-ESP32DEVKIT1-NOHOLES | U$2 | |
1 | ULN2003AN | ULN2003AN | DIL16 | U$3 | DRIVER ARRAY |
Here are some pictures from revision 1 and 2: It was nice with you.
Next a few pictures of the production of revision 3, the soldering and the following connection.
We’ve got three power sources on the whole setup. Via the ESP32 and the integrated LM1017 we have a 3.3V source at max. 1A. In idle mode the ESP32 needs about 200mA. The built-in ATX power supply provides the system with the necessary 12V and 5V. The 12 V are used for the stepper motors. To start the power supply directly, two cables must be connected to each other:
A long time ago we removed pumps and magnetic valves from old blood pressure monitors. Finally they have a purpose. The pumps run without problems up to 12 V, but should be operated at 5 V. The magnetic valves close when voltage is applied. For these pumps we have made an additional PCB so that we can connect everything with the cables more easily. This looked like this and was then attached directly to the pumps.
These were then attached as follows:
We connected the 8 servos to the 16-Channel 12-bit PWM/Servo Driver - I2C interface - PCA9685. The power supply is then connected via a separate power connection. The color coding should be observed.
Then we did the first tests step by step. The servos were controlled alternately left and right:
Then the stepper motor had to be connected and react to the end stop as interrupt. It works. Then we calibrated steps that 10cm were really 10cm.
The pumps could be controlled: The water bubbled.
The ESP32 has opened a WLAN access point. You could then connect to it, which looked like this depending on the device:
Now some more pictures of the machine as it looked at the end:
The cocktail machine outdoors:
Some lines have been programmed. On the one hand the control for the stepper motors, the servos via the PWM servo driver, the end stop, the control of the pumps and the interface. Accordingly the whole code follows now:
#include <WiFi.h>
#include <WiFiClient.h>
#include <WiFiAP.h>
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
#include "settings.h"
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
WiFiServer server(80);
boolean homed = false;
struct endstop {
const uint8_t PIN;
uint32_t numberKeyPresses;
boolean pressed;
};
endstop end1 = {endstop_pin, 0, false};
// everytime an interrupt has been recognized, number will be count up and "pressed" will be true to call next function
void IRAM_ATTR isr() {
//end1.numberKeyPresses += 1;
end1.pressed = true;
}
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("WANNA DRINK SOMETHING?");
// init onboard LED for debugging
// pinMode(LED_BUILTIN, OUTPUT);
// init motor pins
pinMode(stepPin, OUTPUT);
pinMode(dirPin, OUTPUT);
pinMode(enablePin, OUTPUT);
for (uint8_t i = 0; i < 7; i++) {
pinMode( pumps[i], OUTPUT);
delay(100);
digitalWrite(pumps[i], LOW);
}
// SERVO PCA9685
pwm.begin();
pwm.setPWMFreq(60); // Analog servos run at ~60 Hz updates
delay(10);
// WIFI
// You can remove the password parameter if you want the AP to be open.
WiFi.softAP(ssid, password);
IPAddress myIP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(myIP);
server.begin();
Serial.println("Server started");
// ENDSTOP
pinMode(end1.PIN, INPUT);
attachInterrupt(end1.PIN, isr, FALLING);
}
uint16_t count = 0;
void loop() {
//checkClient();
// first, check if any endstop has been hit before action will start
static uint32_t lastMillis = 0;
if (end1.pressed) {// && millis() - lastMillis > debounceTime_endstop) {
Serial.printf("ENDSTOP 1 has been pressed %u times\n", end1.numberKeyPresses);
end1.pressed = false;
homed = true;
}
if (homed == false) {
digitalWrite(enablePin, LOW); // Enables the motor to move in a particular direction
//delay(10);
digitalWrite(dirPin, 0); // Enables the motor to move in a particular direction
digitalWrite(stepPin, HIGH);
delayMicroseconds(70);
digitalWrite(stepPin, LOW);
delayMicroseconds(70);
}
else {
digitalWrite(enablePin, HIGH); // Enables the motor to move in a particular direction
checkClient();
}
}
void checkClient() {
// WIFI ACTION
WiFiClient client = server.available(); // listen for incoming clients
if (client && cocktailReady == true) { // if you get a client,
Serial.println("New Client."); // print a message out the serial port
count = 0;
String currentLine = ""; // make a String to hold incoming data from the client
while (client.connected()) { // loop while the client's connected
if (client.available()) { // if there's bytes to read from the client,
char c = client.read(); // read a byte, then
Serial.write(c); // print it out the serial monitor
if (c == '\n') { // if the byte is a newline character
// if the current line is blank, you got two newline characters in a row.
// that's the end of the client HTTP request, so send a response:
if (currentLine.length() == 0) {
// HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
// and a content-type so the client knows what's coming, then a blank line:
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println();
// use HTML autorefresh to update values on http
// simple example, refresh and print now value of count
// count will be count up by one each refresh
client.println("<meta http-equiv='refresh' content='10; URL=/' />");
count++;
client.println("Count: " + (String)count);
if (count > 10) {
count = 0;
}
// the content of the HTTP response follows the header:
client.print("Click <a href=\"/cocktail1\">here</a> to select cocktail #1 <br>");
client.print("Click <a href=\"/cocktail2\">here</a> to select cocktail #2 <br>");
// The HTTP response ends with another blank line:
client.println();
// break out of the while loop:
break;
} else { // if you got a newline, then clear currentLine:
currentLine = "";
}
} else if (c != '\r') { // if you got anything else but a carriage return character,
currentLine += c; // add it to the end of the currentLine
}
// Check to see if the client request was "GET /H" or "GET /L":
if (currentLine.endsWith("GET /cocktail1") && cocktailReady == true) {
//cocktail = 1;
cocktailReady == false;
// stepper motor
stepperTask(1, 32); // alc#5
delay(1000); // One second delay
servoTask(5);
delay(5000);
servoTask(5);
delay(2000); // One second delay
stepperTask(1, 24); // juice
delay(1000); // One second delay
pump(1, 10000);
delay(5000);
homed = false;
end1.pressed = false;
// call servo action
// servoTask(0);
homed = false;
end1.pressed = false;
cocktail = 0;
cocktailReady == true;
#ifdef debugEN
Serial.println("cocktail 1 is ready");
Serial.println("enjoy your drink and have nice time");
#endif
}
if (currentLine.endsWith("GET /cocktail2") && cocktailReady == true) {
cocktailReady == false;
cocktailReady == true;
#ifdef debugEN
Serial.println("cocktail 2 is ready");
Serial.println("enjoy your drink and have nice time");
#endif
}
}
}
// close the connection:
client.stop();
Serial.println("Client Disconnected.");
}
}
void stepperTask(boolean dir, uint16_t mm) {
#ifdef debugEN
Serial.printf("move cup by %d \n", mm);
#endif
digitalWrite(enablePin, LOW); // Enables the motor to move in a particular direction
delay(10);
digitalWrite(dirPin, dir); // Enables the motor to move in a particular direction
for (int16_t x = 0; x < (stepsMM * mm); x++) {
digitalWrite(stepPin, HIGH);
delayMicroseconds(stepperBreaktime);
digitalWrite(stepPin, LOW);
delayMicroseconds(stepperBreaktime);
}
digitalWrite(enablePin, HIGH); // Enables the motor to move in a particular direction
}
void servoTask(uint8_t servonum) {
#ifdef debugEN
Serial.printf("Select Servo: %d \n", servonum);
#endif
for (uint16_t pulselen = servomax; pulselen > servomin; pulselen--) {
pwm.setPWM(servonum, 0, pulselen);
}
delay(servoBreaktime);
for (uint16_t pulselen = servomin; pulselen < servomax; pulselen++) {
pwm.setPWM(servonum, 0, pulselen);
}
}
void pump(int nr, uint16_t breaktime_pump) {
digitalWrite( pumps[nr], HIGH);
#ifdef debugEN
Serial.printf("Starting pumps: %d for %d \n", pumps[nr], breaktime_pump);
#endif
delay(breaktime_pump);
digitalWrite( pumps[nr], LOW);
}
Und noch die dazugehörige settings.h:
#define debugEN // un/-comment to en-/disable of extra information by Serial Port
/* STEPPER SETTINGS */
// defines pins numbers
#define stepPin 12
#define dirPin 14
#define enablePin 13
#define stepperBreaktime 70 // how long breaktime between each step, shorter will block motor
const int stepsMM = 320; // calc steps for one mm
#define endstop_pin 25
#define debounceTime_endstop 10000
/* SERVO SETTINGS */
// Depending on your servo make, the pulse width min and max may vary, you
// want these to be as small/large as possible without hitting the hard stop
// for max range. You'll have to tweak them as necessary to match the servos you
// have!
#define servomin 150 // standard 150 this is the 'minimum' pulse length count (out of 4096)
#define servomax 600 // standard 600 this is the 'maximum' pulse length count (out of 4096)
#define servoBreaktime 5000
/* COCKTAIL */
uint8_t cocktail = 0;
/* WIFI SETTINGS */
// Set these to your desired credentials.
const char *ssid = "cocktail_time";
const char *password = "NixFuerKinder";
boolean cocktailReady = true;
/* PUMPS */
const int pumps[] = {2,4,16,17,5,18};
#define pumps_1 2
#define pumps_2 4
#define pumps_3 16
#define pumps_4 17
#define pumps_5 5
#define pumps_6 18
#define pumps_7 19
Also a video of the assembly and all the tests that were done.
Last but not least, a final video on how the process can work:
The cocktail machine closes with the fact that it still has a lot of potential. There were still countless ideas to be explored after the Fab Academy. Further information will be published soon at www.dezentrale-dortmund.de.
Here you can download all the files:
Arduino Sketch
Cocktail board
Pump board