Machine Design

Week 17

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!

Designing the circuit board

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
1PINHD-1X31X03ENDSTOPPIN HEADER
2PINHD-1X41X04I2C, STEPPERMOTORPIN HEADER
1PINHD-2X22X02POWER_+12VPIN HEADER
2PINHD-2X32X03MS, RGBPIN HEADER
1PINHD-2X42X04POWER_+5VPIN HEADER
1PINHD-2X62X06EXTRA_INPIN HEADER
1PINHD-2X72X07PUMPPIN HEADER
11.8KR-EU_R1206R1206R10RESISTOR, European symbol
2100nFC-EUC1206C1206C3, C9CAPACITOR, European symbol
210KR-EU_R1206R1206R1, R3RESISTOR, European symbol
11KR-EU_R1206R1206R6RESISTOR, European symbol
11uFC-EUC1206C1206C4CAPACITOR, European symbol
1200mA/40VTRANS_PNP-PMBT3906|MMBT3906LSOT23-3MBT2N3906PNP transistor
13.3KR-EU_R1206R1206R2RESISTOR, European symbol
24.7KR-EU_R1206R1206R7, R8RESISTOR, European symbol
2470uFCPOL-EUE3.5-8E3,5-8C1, C2POLARIZED CAPACITOR, European symbol
1DRV8825DRV8825DRV8825U$1
1ESP32-DEVKIT1_NOHOLESESP32-DEVKIT1_NOHOLESNODELUA-ESP32DEVKIT1-NOHOLESU$2
1ULN2003ANULN2003ANDIL16U$3DRIVER ARRAY

PCB production

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.

Connecting everything

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



Have a look at other weekly assignments