Week 17. Machine Design

17. Machine Design

Idea

We had a idea to make a marble machine calendar that displays the Waag Machine Scheduler.
For the full start of the idea and how we got the the final idea that we created please read our group page

Anne’s birthday

It was Anne’s birthday this week, and we celebrated with cherry pie (kersenvlaai).
I quickly connected a speaker to the Arduini pro mini and downloaded a Happy Birthday tune.
I connected the speaker to pin 9 (PWM) with a 1k Ohm resistor. The resistor was bit too strong, resulting in a soft sound, but it worked.

A speaker connected to the Arduino pro mini

/* Code from
** https://create.arduino.cc/projecthub/trduunze/piezo-happy-birthday-7ea362
* 
* tempo speeds up after every loop, just for fun.
 */
 
int speakerPin = 9;
int length = 28; // the number of notes
char notes[] = "GGAGcB GGAGdc GGxecBA yyecdc";
int beats[] = { 2, 2, 8, 8, 8, 16, 1, 2, 2, 8, 8, 8, 16, 1, 2, 2, 8, 8, 8, 8, 16, 1, 2, 2, 8, 8, 8, 16 };
int tempo = 150;

void playTone(int tone, int duration) {
  for (long i = 0; i < duration * 1000L; i += tone * 2) {
    digitalWrite(speakerPin, HIGH);
    delayMicroseconds(tone);
    digitalWrite(speakerPin, LOW);
    delayMicroseconds(tone);
  }
}

void playNote(char note, int duration) {
  char names[] = {'C', 'D', 'E', 'F', 'G', 'A', 'B',
                  'c', 'd', 'e', 'f', 'g', 'a', 'b',
                  'x', 'y'
                 };
  int tones[] = { 1915, 1700, 1519, 1432, 1275, 1136, 1014,
                  956,  834,  765,  593,  468,  346,  224,
                  655 , 715
                };
  int SPEE = 5;
  // play the tone corresponding to the note name
  for (int i = 0; i < 17; i++) {
    if (names[i] == note) {
      int newduration = duration / SPEE;
      playTone(tones[i], newduration);
    }
  }
}

void setup() {
  pinMode(speakerPin, OUTPUT);
}

void loop() {
  for (int i = 0; i < length; i++) {
    if (notes[i] == ' ') {
      delay(beats[i] * tempo); // rest
    } else {
      playNote(notes[i], beats[i] * tempo);
    }

    // pause between notes
    delay(tempo);
  }
  tempo = abs(tempo - 10) % 150;
}

Assignment

Group assignment

Design a machine (mechanism + actuation + automation)
Build the mechanical parts and operate it manually.
Document the group project

Individual assignment

Document your individual contribution.

Software

Arduino IDE, for programming CoolTerm, for sending and receiving Serial data in HEX

Hardware

USB Power measurement device
Arduino Pro mini
Servo’s
Multimeter Roland Modella MDX-20 mill
Roland Modella MDX-20 mill
with the mill bit for traces - mini end mills two flutes 0.40 mm 826 (damen.cc)
with the mill bit for cutout - two flutes 0.8 mm
Knife
Anti static tweezers for electrical components
Solder iron
Solder tin
Headers
Rainbow calbes
A regulated digital controlled DC power supply

Files

Code running on the Arduino’s controlling the servo’s
Code running on the master Arduino connected to the buttons and the other Arduino’s
Servo test code via serial controllable servo’s
Mod files for Servo controller board V2

Controlling 20 Servo’s

For controlling the gates of our marble rail, we want to use servo’s.
We have 20 gates, that should be controlled individually, meaning we need to controll 20 servo’s.
Servo’s are controlled using a PWM pin of the microntroller.
The Atmel chips we used in the past weeks do not have 20 PWM pins, an ATTiny44 has 4 PWM pins, an Atmel Atmega 328 has 6 PWM pins.
So if we do not create a fancy design, we need to use at least 4 Atmega328 chips, we have these available in the form of Arduino pro mini’s.
The Arduino mega, with the Atmel

Connecting a servo

On the Arduino websiteis explained how you connect a servo to your microcontroller.
Our servo has 3 wires: red, brown and orange. We connect the wires:

  • red : 5V
  • brown : ground
  • orange : PWM pin of your microcontroller

To test if our connection works, I loaded the servo Sweep example of Scott Fitzgerald and modified it slightly to implement a delay of 5000 miliseconds inbetween sweeps.
The code uses the Servo library, which is documented here. After connecting the servo, I observed it sweeping 180 degrees. I modified the degrees to 90 degrees, better resembling the desired behavior the servo arm should have when mounted and pushin away the marble:

/* Sweep
 by BARRAGAN <http://barraganstudio.com>
 This example code is in the public domain.

 modified 8 Nov 2013
 by Scott Fitzgerald
 http://www.arduino.cc/en/Tutorial/Sweep
*/

#include <Servo.h>

Servo myservo;  // create servo object to control a servo
// twelve servo objects can be created on most boards

int pos = 0;    // variable to store the servo position

void setup() {
  myservo.attach(9);  // attaches the servo on pin 9 to the servo object
}

void loop() {
  for (pos = 0; pos <= 90; pos += 1) { // goes from 0 degrees to 180 degrees
    // in steps of 1 degree
    myservo.write(pos);              // tell servo to go to position in variable 'pos'
    delay(15);                       // waits 15ms for the servo to reach the position
  }
  delay(5000);
  for (pos = 90; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
    myservo.write(pos);              // tell servo to go to position in variable 'pos'
    delay(15);     // waits 15ms for the servo to reach the position
  }
  delay(5000);
}

One connected servo to our Arduino

I connected a power meter in between the microcontroller and the power supply to measure the current usage.
In rest the microcontroller setup uses 6 mA.
When the servo is active the setup uses 11 mA.
I concluded from this small test that our servo uses (11 - 6 = ) 5 mA. To be save, I asume the maximum load will be twice as much, with some measurment error, coming down to 11mA. We want to connect 20 servo’s and 4 microcontrollers, so our total peek current usage is: 11 mA * 20 = 220 mA
6 mA * 4 = 24 mA


  •       244 mA peek current usage  
    

The average power supply and USB port can deliver about 1 or 2 Ampere, meaning we can power all our devices with one power supply without reaching the limits.
Even if my measurements and calculations are off by a factor of 3, we still are below the 1A.

After determining our setup is feasable with a default power supply, I extended the Sweep code to controll the maximum servo’s ( 6 ) we can controll with our Arduino pro mini. Looking at our pinlayout the PWM pins we can use are: 3,5,6,9,10 and 11.

/* Multiple Servo Sweep
 by BARRAGAN <http://barraganstudio.com>
 This example code is in the public domain.

 modified 8 Nov 2013
 by Scott Fitzgerald
 http://www.arduino.cc/en/Tutorial/Sweep

 modified 15 May 2019
 by Joey van der Bie
 http://fab.academany.org/2019/labs/waag/students/josephus-vanderbie//week17.html
*/

#include <Servo.h>

Servo servo1, servo2, servo3, servo4, servo5, servo6;  // create servo object to control a servo
// twelve servo objects can be created on most boards

int pos = 0;    // variable to store the servo position

void setup() {
  servo1.attach(3);  // attaches the servo on pin 9 to the servo object
  servo2.attach(5);
  servo3.attach(6);
  servo4.attach(9);
  servo5.attach(10);
  servo6.attach(11);
}

void loop() {
  for (pos = 0; pos <= 90; pos += 1) { // goes from 0 degrees to 180 degrees
    // in steps of 1 degree
    servo1.write(pos);              // tell servo to go to position in variable 'pos' 
    servo2.write(pos); 
    servo3.write(pos); 
    servo4.write(pos); 
    servo5.write(pos); 
    servo6.write(pos); 
    delay(15);                       // waits 15ms for the servo to reach the position
  }
  delay(5000);
  for (pos = 90; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
    servo1.write(pos);              // tell servo to go to position in variable 'pos'
    servo2.write(pos); 
    servo3.write(pos); 
    servo4.write(pos); 
    servo5.write(pos); 
    servo6.write(pos); 
    delay(15);     // waits 15ms for the servo to reach the position
  }
  delay(5000);
}

I connected first 4 servo’s to the microcontroller, using a lot of wires and the breadbord (sorry Neal) to breack out the 5V and GND lines to all our devices ( 1 microcontroller + 4 servo’s).
Running the code controlling all the 6 servo’s at once, showed perfomance was flacky at best.
Sometimes it all worked perfect, other times, not all servo’s moved the full 90 degrees.
There were even loops where the servo’s moved slower than intended.
Also after several minutes of trying my Macbook and Arduino environment started to be buggy.
In the Arduino environment the FTDI controller kept disapearing.
Only after disconnecting the FTDI controller from the computer and microcontroller and using a different USB port, it was recognised again by my computer.
This behavior woried me, since I was not even using 6 servo’s.

To ease the load, I modified the code to controll each servo in sequence for the movement from 0 to 90 degrees and all together from 90 to 0 degrees.
This allowed me better to compare the load and accuracy of the devices.

/* Multiple Servo Sweep in Sequence
 by BARRAGAN <http://barraganstudio.com>
 This example code is in the public domain.

 modified 8 Nov 2013
 by Scott Fitzgerald
 http://www.arduino.cc/en/Tutorial/Sweep

 modified 15 May 2019
 by Joey van der Bie
 http://fab.academany.org/2019/labs/waag/students/josephus-vanderbie//week17.html
*/


#include <Servo.h>

Servo servo1, servo2, servo3, servo4, servo5, servo6;  // create servo object to control a servo
// twelve servo objects can be created on most boards

int pos = 0;    // variable to store the servo position
int delayTime = 2;

void setup() {
  servo1.attach(3);  // attaches the servo on pin 9 to the servo object
  servo2.attach(5);
  servo3.attach(6);
  servo4.attach(9);
  servo5.attach(10);
  servo6.attach(11);
}

void loop() {
  for (pos = 0; pos <= 90; pos += 1) { // goes from 0 degrees to 180 degrees
    // in steps of 1 degree
    servo1.write(pos);              // tell servo to go to position in variable 'pos' 
    delay(delayTime);                       // waits 15ms for the servo to reach the position
  }
    for (pos = 0; pos <= 90; pos += 1) { // goes from 0 degrees to 180 degrees
    // in steps of 1 degree
    servo2.write(pos); 
    delay(delayTime);                       // waits 15ms for the servo to reach the position
  }
  for (pos = 0; pos <= 90; pos += 1) { // goes from 0 degrees to 180 degrees
    // in steps of 1 degree
    servo3.write(pos); 
    delay(delayTime);                       // waits 15ms for the servo to reach the position
  }
    for (pos = 0; pos <= 90; pos += 1) { // goes from 0 degrees to 180 degrees
    // in steps of 1 degree
    servo4.write(pos); 
    delay(delayTime);                       // waits 15ms for the servo to reach the position
  }
      for (pos = 0; pos <= 90; pos += 1) { // goes from 0 degrees to 180 degrees
    // in steps of 1 degree
    servo5.write(pos); 
    delay(delayTime);                       // waits 15ms for the servo to reach the position
  }
      for (pos = 0; pos <= 90; pos += 1) { // goes from 0 degrees to 180 degrees
    // in steps of 1 degree
    servo6.write(pos); 
    delay(delayTime);                       // waits 15ms for the servo to reach the position
  }
  delay(5000);

  for (pos = 90; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
    servo1.write(pos);              // tell servo to go to position in variable 'pos'
    servo2.write(pos); 
    servo3.write(pos); 
    servo4.write(pos); 
    servo5.write(pos); 
    servo6.write(pos); 
    delay(delayTime);     // waits 15ms for the servo to reach the position
  }
  delay(5000);
}

Looking at our power measurement device, there is a big difference between the load.
When 4 servo’s are used at the same time, we see a maximum load of 45 mA. This is pretty high, but still below my maximum load calculation of ( 6 + (4 * 11) = ) 50 mA.
It appears I was not far off asuming a servo would use 11 mA on maximum load.

When we look at the power usage when the servo’s are used in sequence, we see a load of about 25 mA.
Also the behavior of the servo’s in sequence was much more consistent and looked prettier.
I decided to keep the servo controll in sequence for our final design.

Video showing the servo’s in sequenc and all together with power measurements

I tried to optimize and prepare the code to be used in combination with Serial communication.
I removed duplicate code and abstracted this to functions and object definitions.
I also introduced a structure (struct), allowing me to store values that belonged to one servo without breaking Arduino compatibility.
To reduce size of the code with a few bytes I defined the integers with a limitation of bits. 3 bits for the servo number (I only need 1 till 6), and 4 bits for the pin number (I only need 1 till 11).

/* 
 *  The Multiple servo controll
 *  This code allows you to controll multiple servo's at the same time adressing them by an integer number( 1 between 6),
 *  and tell them to switch state (1 or 0) for open or closed,
 *  
 *  http://fab.academany.org/2019/labs/waag/students/josephus-vanderbie//week17.html
 *  made by Joey van der Bie
 *  2019-05-13
 *  
 *  This code is based on the Sweep example
 by BARRAGAN <http://barraganstudio.com>
 This example code is in the public domain.

 modified 8 Nov 2013
 by Scott Fitzgerald
 http://www.arduino.cc/en/Tutorial/Sweep
*/

#include <Servo.h>

Servo servo1, servo2, servo3, servo4, servo5, servo6;  // create servo object to control a servo
// twelve servo objects can be created on most boards

int pos = 0;    // variable to store the servo position
int delayTime = 5; // delay between servo steps
int degreeSteps = 1; // steps to take between degrees
int delayBetweenservo's = 200; // delay between the movement of servo's

int servo'sTATE_OPEN = 0; //degrees position for open
int servo'sTATE_CLOSED = 90; // degrees position for closed

//The servo structure 
struct servo'struct {
  unsigned int  servoNumber:3; // the number we addres from our serial communication
  unsigned int   pinNumber:4; // the pin it is connected to
  int   currentPosition; // current position in degrees
   Servo  servoObject; // the actual object 
};

struct servo'struct ss1, ss2, ss3, ss4, ss5, ss6;


void setup() {
  ss1.servoNumber = 1;
  ss1.pinNumber = 3;
  ss1.currentPosition = 0;
  ss1.servoObject = servo1;
  ss1.servoObject.attach(ss1.pinNumber);
  ss1.servoObject.write(0);

  ss2.servoNumber = 2;
  ss2.pinNumber = 5;
  ss2.currentPosition = 0;
  ss2.servoObject = servo2;
  ss2.servoObject.attach(ss2.pinNumber);
  ss2.servoObject.write(0);
  
  ss3.servoNumber = 3;
  ss3.pinNumber = 6;
  ss3.currentPosition = 0;
  ss3.servoObject = servo3;
  ss3.servoObject.attach(ss3.pinNumber);
  ss3.servoObject.write(0);
  
  ss4.servoNumber = 4;
  ss4.pinNumber = 9;
  ss4.currentPosition = 0;
  ss4.servoObject = servo4;
  ss4.servoObject.attach(ss4.pinNumber);
  ss4.servoObject.write(0);
  
  ss5.servoNumber = 5;
  ss5.pinNumber = 10;
  ss5.currentPosition = 0;
  ss5.servoObject = servo5;
  ss5.servoObject.attach(ss5.pinNumber);
  ss5.servoObject.write(0);
  
  ss6.servoNumber = 6;
  ss6.pinNumber = 11;
  ss6.currentPosition = 0;
  ss6.servoObject = servo6;
  ss6.servoObject.attach(ss6.pinNumber);
  ss6.servoObject.write(0);
}

void loop() {
  servoToState(1,servo'sTATE_CLOSED); 
  delay(delayBetweenservo's);
    servoToState(2,servo'sTATE_CLOSED); 
  delay(delayBetweenservo's);
    servoToState(3,servo'sTATE_CLOSED); 
  delay(delayBetweenservo's);
    servoToState(4,servo'sTATE_CLOSED); 
  delay(delayBetweenservo's);
    servoToState(5,servo'sTATE_CLOSED); 
  delay(delayBetweenservo's);
    servoToState(6,servo'sTATE_CLOSED); 
  delay(5000);
  servoToState(1,servo'sTATE_OPEN); 
    delay(delayBetweenservo's);
    servoToState(2,servo'sTATE_OPEN); 
  delay(delayBetweenservo's);
    servoToState(3,servo'sTATE_OPEN); 
  delay(delayBetweenservo's);
    servoToState(4,servo'sTATE_OPEN); 
  delay(delayBetweenservo's);
    servoToState(5,servo'sTATE_OPEN); 
  delay(delayBetweenservo's);
    servoToState(6,servo'sTATE_OPEN); 
    delay(5000);

}

void servoToState(int servoNr, int state){
  if(ss1.servoNumber == servoNr){
    moveServo(&ss1, state);
  }else   if(ss2.servoNumber == servoNr){
    moveServo(&ss2, state);
  }else   if(ss3.servoNumber == servoNr){
    moveServo(&ss3, state);
  }else   if(ss4.servoNumber == servoNr){
    moveServo(&ss4, state);
  }else   if(ss5.servoNumber == servoNr){
    moveServo(&ss5, state);
  }else   if(ss6.servoNumber == servoNr){
    moveServo(&ss6, state);
  }
}

void moveServo(struct servo'struct *servo, int newPosition){
  if(servo->currentPosition < newPosition){
    for (pos = servo->currentPosition; pos <= newPosition; pos += degreeSteps) { // goes from 0 degrees to 180 degrees
      servo->servoObject.write(pos); 
      delay(delayTime);                       // waits 15ms for the servo to reach the position
    }
  }else{
        for (pos = servo->currentPosition; pos >= newPosition; pos -= degreeSteps) { // goes from 0 degrees to 180 degrees
      servo->servoObject.write(pos); 
      delay(delayTime);                       // waits 15ms for the servo to reach the position
    }
  }
  servo->currentPosition = newPosition;
}

After confirming my new code worked, I further extend it with serial support.

Ardino Pro mini Pin Layout with Atmel329, image by theengineeringprojects.com

/* 
 *  The Multiple servo controller with Serial controll
 *  This code allows you to controll multiple servo's at the same time adressing them by an integer number( 1 between 6),
 *  and tell them to switch state (1 or 0) for open or closed,
 *  using the Serial interface
 *  
 *  http://fab.academany.org/2019/labs/waag/students/josephus-vanderbie//week17.html
 *  made by Joey van der Bie
 *  2019-05-13
 *  
 *  This code is based on the Sweep example
 by BARRAGAN <http://barraganstudio.com>
 This example code is in the public domain.

 modified 8 Nov 2013
 by Scott Fitzgerald
 http://www.arduino.cc/en/Tutorial/Sweep
*/


byte servoNumber = B0000000;
byte servo'state = B0000000;
byte emptyValue = B1000000; //this is the empty value for our servoNumber and servo'state veriables that allows us to check if it is not set.

bool stringComplete = false;  // whether the string is complete


#include <Servo.h>

Servo servo1, servo2, servo3, servo4, servo5, servo6;  // create servo object to control a servo
// twelve servo objects can be created on most boards

int pos = 0;    // variable to store the servo position
int delayTime = 5; // delay between servo steps
int degreeSteps = 1; // steps to take between degrees
int delayBetweenservo's = 200; // delay between the movement of servo's

int servo'sTATE_OPEN = 1; //indicator for communication via serial
int servo'sTATE_CLOSED = 0; //indicator for communication via serial

int servo'sTATE_OPEN_POSITION = 2;//degrees position for open
int servo'sTATE_CLOSED_POSITION = 92;// degrees position for closed

//The servo structure 
struct servo'struct {
  unsigned int  servoNumber:3; // the number we addres from our serial communication
  unsigned int   pinNumber:4; // the pin it is connected to
  int   currentPosition; // current position in degrees
   Servo  servoObject; // the actual object 
};

struct servo'struct ss1, ss2, ss3, ss4, ss5, ss6;


void setup() {
  Serial.begin(9600);
  
  ss1.servoNumber = 1;
  ss1.pinNumber = 3;
  ss1.currentPosition = 0;
  ss1.servoObject = servo1;
  ss1.servoObject.attach(ss1.pinNumber);
  ss1.servoObject.write(servo'sTATE_OPEN_POSITION);

  ss2.servoNumber = 2;
  ss2.pinNumber = 5;
  ss2.currentPosition = 0;
  ss2.servoObject = servo2;
  ss2.servoObject.attach(ss2.pinNumber);
  ss2.servoObject.write(servo'sTATE_OPEN_POSITION);
  
  ss3.servoNumber = 3;
  ss3.pinNumber = 6;
  ss3.currentPosition = 0;
  ss3.servoObject = servo3;
  ss3.servoObject.attach(ss3.pinNumber);
  ss3.servoObject.write(servo'sTATE_OPEN_POSITION);
  
  ss4.servoNumber = 4;
  ss4.pinNumber = 9;
  ss4.currentPosition = 0;
  ss4.servoObject = servo4;
  ss4.servoObject.attach(ss4.pinNumber);
  ss4.servoObject.write(servo'sTATE_OPEN_POSITION);
  
  ss5.servoNumber = 5;
  ss5.pinNumber = 10;
  ss5.currentPosition = 0;
  ss5.servoObject = servo5;
  ss5.servoObject.attach(ss5.pinNumber);
  ss5.servoObject.write(servo'sTATE_OPEN_POSITION);
  
  ss6.servoNumber = 6;
  ss6.pinNumber = 11;
  ss6.currentPosition = 0;
  ss6.servoObject = servo6;
  ss6.servoObject.attach(ss6.pinNumber);
  ss6.servoObject.write(servo'sTATE_OPEN_POSITION);
}

void loop() {
//  servoToState(1,servo'sTATE_CLOSED); 
  // print the string when a newline arrives:
  if (stringComplete) {
    //Serial.print("number: ");
    Serial.write((int)servoNumber);
    //Serial.println("");
    //Serial.print("state: ");
    Serial.write((int)servo'state);
    
    servoToState((int)servoNumber, (int)servo'state);
    resetSerialStorageValues();
  }

}

void servoToState(int servoNr, int state){
  if(state == servo'sTATE_OPEN){
    state = servo'sTATE_OPEN_POSITION;
  }else if(state == servo'sTATE_CLOSED){
    state = servo'sTATE_CLOSED_POSITION;
  }else {
    //invalid state, stop the function
        //Serial.print("invalid state:");
        //Serial.println(state, BIN);
        //Serial.println(servo'sTATE_OPEN, BIN);
        //Serial.println(servo'sTATE_CLOSED, BIN);
    return;
  }
  
  if(ss1.servoNumber == servoNr){
    moveServo(&ss1, state);
  }else   if(ss2.servoNumber == servoNr){
    moveServo(&ss2, state);
  }else   if(ss3.servoNumber == servoNr){
    moveServo(&ss3, state);
  }else   if(ss4.servoNumber == servoNr){
    moveServo(&ss4, state);
  }else   if(ss5.servoNumber == servoNr){
    moveServo(&ss5, state);
  }else   if(ss6.servoNumber == servoNr){
    moveServo(&ss6, state);
  }else{
      //Serial.print("No servo's found for number:");
      //Serial.println(servoNr, BIN);
      //Serial.println(ss1.servoNumber, BIN);
      //Serial.println(ss2.servoNumber, BIN);
      //Serial.println(ss3.servoNumber, BIN);
      //Serial.println(ss4.servoNumber, BIN);
      //Serial.println(ss5.servoNumber, BIN);
      //Serial.println(ss6.servoNumber, BIN);
  }
}

void moveServo(struct servo'struct *servo, int newPosition){
  //Serial.print("Moving servo");
  //Serial.println(servo->servoNumber);
  if(servo->currentPosition < newPosition){
    for (pos = servo->currentPosition; pos <= newPosition; pos += degreeSteps) { // goes from 0 degrees to 180 degrees
      servo->servoObject.write(pos); 
      delay(delayTime);                       // waits 15ms for the servo to reach the position
    }
  }else{
        for (pos = servo->currentPosition; pos >= newPosition; pos -= degreeSteps) { // goes from 0 degrees to 180 degrees
      servo->servoObject.write(pos); 
      delay(delayTime);                       // waits 15ms for the servo to reach the position
    }
  }
  servo->currentPosition = newPosition;
}

/*
  SerialEvent occurs whenever a new data comes in the hardware serial RX. This
  routine is run between each time loop() runs, so using delay inside loop can
  delay response. Multiple bytes of data may be available.
*/
void serialEvent() {
  while (Serial.available()) {
    byte incommmingByte = Serial.read();
    if ((char)incommmingByte == '\n') {
      if(servoNumber == emptyValue || servo'state == emptyValue){
        //we have missed a crucial value, reset our ledger.
        resetSerialStorageValues();
      }else{
        //everything checks out, lets process the command
        stringComplete = true;
      }
    }else if(servoNumber == emptyValue){
      servoNumber = B0001111 & incommmingByte;
    }else if(servo'state == emptyValue){
      //maks the incomming byte and limit it to one bit (0 or 1);
      servo'state =  B0001111 & incommmingByte;
    }else{
      //something went wrong
      //continue by storing the values, but move them up a place
      servoNumber = servo'state;
      servo'state = incommmingByte;
    }
  }
}

void resetSerialStorageValues(){
    stringComplete = false;
    servoNumber = emptyValue;
    servo'state = emptyValue;
}

Micky and Anne made a cable that allowed each servo to be controlled and be connected to the power and the ground.
We attached all the devices to these cables and fitted them to the marble rail I milled earlier.
This is us attaching the servo's All the servo's and Arduino's together Servo's attached to the marble rail

After applying the servo’s and our boards to the marble rail, we saw not all servo arms were mounted at the same position.
I modified the code to allow each servo to have a different OPEN and CLOSED position.

/* 
 *  The Multiple servo controller with Serial controll
 *  This code allows you to controll multiple servo's at the same time adressing them by an integer number( 1 between 6),
 *  and tell them to switch state (1 or 0) for open or closed, the exact open and close position of each servo con be determined using the struct.
 *  using the Serial interface the servo's can be controlled.  
 *  For setting servo 1 open send: 0x01 0x01 0xFF
 *  For setting servo 20 closed send: 0x13 0x00 0xFF 
 *  0xFF is de end bit.
 *  The serailEvent handling is borrowed and modified from Tom Igoe serial event example http://www.arduino.cc/en/Tutorial/SerialEvent
 *  
 *  http://fab.academany.org/2019/labs/waag/students/josephus-vanderbie//week17.html
 *  made by Joey van der Bie
 *  2019-05-21
 *  
 *  This code is based on the Sweep example
 by BARRAGAN <http://barraganstudio.com>
 This example code is in the public domain.

 modified 8 Nov 2013
 by Scott Fitzgerald
 http://www.arduino.cc/en/Tutorial/Sweep
*/
//CHECK OR UPDATE THESE NUMBERS BEFORE UPLOADING!!!!
int ARDUINO_NUMBER = 1; //range 1,2,3 or 4 (Arduino position in servo)
int NUMBER_OF_servo's = 5; //number of servo's per Arduino;
//CHECK OR UPDATE THESE NUMBERS BEFORE UPLOADING!!!!

byte servoNumber = B0000000;
byte servo'state = B0000000;
byte emptyValue = B1000000; //this is the empty value for our servoNumber and servo'state veriables that allows us to check if it is not set.
byte serialProcessBitMask = B00011111;
byte endByte = B11111111;

bool stringComplete = false;  // whether the string is complete


#include <Servo.h>

Servo servo1, servo2, servo3, servo4, servo5, servo6;  // create servo object to control a servo
// twelve servo objects can be created on most boards

int pos = 0;    // variable to store the servo position
int delayTime = 5; // delay between servo steps
int degreeSteps = 1; // steps to take between degrees
int delayBetweenservo's = 200; // delay between the movement of servo's

int servo'sTATE_OPEN = 1; //indicator for communication via serial
int servo'sTATE_CLOSED = 0; //indicator for communication via serial

//default positions (not normally used)
int servo'sTATE_OPEN_POSITION = 2;//degrees position for open
int servo'sTATE_CLOSED_POSITION = 95;// degrees position for closed

//The servo structure 
struct servo'struct {
  unsigned int  servoNumber:5; // the number we addres from our serial communication, the :5 is the number of bits used
  unsigned int  pinNumber:4; // the pin it is connected to, the :4 is the number of bits
  int   currentPosition; // current position in degrees
   Servo  servoObject; // the actual object 
   int OPEN_POSITION; // open position (anything between 0 and 180)
   int CLOSED_POSITION;// closed position (anything between 0 and 180)
};

struct servo'struct ss1, ss2, ss3, ss4, ss5, ss6;


void setup() {
  Serial.begin(9600);
  
  ss1.servoNumber = (ARDUINO_NUMBER-1)*NUMBER_OF_servo's + 1;
  ss1.pinNumber = 3;
  ss1.currentPosition = 0;
  ss1.OPEN_POSITION = servo'sTATE_OPEN_POSITION;
  ss1.CLOSED_POSITION = servo'sTATE_CLOSED_POSITION;
  ss1.servoObject = servo1;
  ss1.servoObject.attach(ss1.pinNumber);
  ss1.servoObject.write(ss1.OPEN_POSITION); // move the servo to its start position

  ss2.servoNumber = (ARDUINO_NUMBER-1)*NUMBER_OF_servo's + 2;
  ss2.pinNumber = 5;
  ss2.currentPosition = 0;
  ss2.OPEN_POSITION = servo'sTATE_OPEN_POSITION;
  ss2.CLOSED_POSITION = servo'sTATE_CLOSED_POSITION;
  ss2.servoObject = servo2;
  ss2.servoObject.attach(ss2.pinNumber);
  ss2.servoObject.write(ss2.OPEN_POSITION);
  
  ss3.servoNumber = (ARDUINO_NUMBER-1)*NUMBER_OF_servo's + 3;
  ss3.pinNumber = 6;
  ss3.currentPosition = 0;
  ss3.OPEN_POSITION = servo'sTATE_OPEN_POSITION;
  ss3.CLOSED_POSITION = servo'sTATE_CLOSED_POSITION;
  ss3.servoObject = servo3;
  ss3.servoObject.attach(ss3.pinNumber);
  ss3.servoObject.write(ss3.OPEN_POSITION);
  
  ss4.servoNumber = (ARDUINO_NUMBER-1)*NUMBER_OF_servo's + 4;
  ss4.pinNumber = 10;
  ss4.currentPosition = 0;
  ss4.OPEN_POSITION = servo'sTATE_OPEN_POSITION;
  ss4.CLOSED_POSITION = servo'sTATE_CLOSED_POSITION;
  ss4.servoObject = servo4;
  ss4.servoObject.attach(ss4.pinNumber);
  ss4.servoObject.write(ss4.OPEN_POSITION);
  
  ss5.servoNumber = (ARDUINO_NUMBER-1)*NUMBER_OF_servo's + 5;
  ss5.pinNumber = 9;
  ss5.currentPosition = 0;
  ss6.OPEN_POSITION = servo'sTATE_OPEN_POSITION;
  ss6.CLOSED_POSITION = servo'sTATE_CLOSED_POSITION;
  ss5.servoObject = servo5;
  ss5.servoObject.attach(ss5.pinNumber);
  ss5.servoObject.write(ss5.OPEN_POSITION);
  
  ss6.servoNumber = (ARDUINO_NUMBER-1)*NUMBER_OF_servo's + 6;
  ss6.pinNumber = 11;
  ss6.currentPosition = 0;
  ss6.OPEN_POSITION = servo'sTATE_OPEN_POSITION;
  ss6.CLOSED_POSITION = servo'sTATE_CLOSED_POSITION;
  ss6.servoObject = servo6;
  ss6.servoObject.attach(ss6.pinNumber);
  ss6.servoObject.write(ss6.OPEN_POSITION);
}

void loop() {
//  servoToState(1,servo'sTATE_CLOSED); 
  // print the string when a newline arrives:
  if (stringComplete) {
    //Serial.print("number: ");
    //Serial.write((int)servoNumber);
    //Serial.println("");
    //Serial.print("state: ");
    //Serial.write((int)servo'state);
    
    servoToState((int)servoNumber, (int)servo'state);
    resetSerialStorageValues();
  }

}

void servoToState(int servoNr, int state){
  if(state == servo'sTATE_OPEN){
   // state = servo'sTATE_OPEN_POSITION;
  }else if(state == servo'sTATE_CLOSED){
   // state = servo'sTATE_CLOSED_POSITION;
  }else {
    //invalid state, stop the function
        //Serial.print("invalid state:");
        //Serial.println(state, BIN);
        //Serial.println(servo'sTATE_OPEN, BIN);
        //Serial.println(servo'sTATE_CLOSED, BIN);
    return;
  }
  
  if(ss1.servoNumber == servoNr){
    moveServo(&ss1, state?ss1.OPEN_POSITION:ss1.CLOSED_POSITION);
  }else   if(ss2.servoNumber == servoNr){
    moveServo(&ss2,  state?ss2.OPEN_POSITION:ss2.CLOSED_POSITION);
  }else   if(ss3.servoNumber == servoNr){
    moveServo(&ss3,  state?ss3.OPEN_POSITION:ss3.CLOSED_POSITION);
  }else   if(ss4.servoNumber == servoNr){
    moveServo(&ss4,  state?ss4.OPEN_POSITION:ss4.CLOSED_POSITION);
  }else   if(ss5.servoNumber == servoNr){
    moveServo(&ss5,  state?ss5.OPEN_POSITION:ss5.CLOSED_POSITION);
  }else   if(ss6.servoNumber == servoNr){
    moveServo(&ss6,  state?ss6.OPEN_POSITION:ss6.CLOSED_POSITION);
  }else{
      //Serial.print("No servo's found for number:");
      //Serial.println(servoNr, BIN);
      //Serial.println(ss1.servoNumber, BIN);
      //Serial.println(ss2.servoNumber, BIN);
      //Serial.println(ss3.servoNumber, BIN);
      //Serial.println(ss4.servoNumber, BIN);
      //Serial.println(ss5.servoNumber, BIN);
      //Serial.println(ss6.servoNumber, BIN);
  }
}

void moveServo(struct servo'struct *servo, int newPosition){
  //Serial.print("Moving servo");
  //Serial.println(servo->servoNumber);
  if(servo->currentPosition < newPosition){
    for (pos = servo->currentPosition; pos <= newPosition; pos += degreeSteps) { // goes from 0 degrees to 180 degrees
      servo->servoObject.write(pos); 
      delay(delayTime);                       // waits 15ms for the servo to reach the position
    }
  }else{
        for (pos = servo->currentPosition; pos >= newPosition; pos -= degreeSteps) { // goes from 0 degrees to 180 degrees
      servo->servoObject.write(pos); 
      delay(delayTime);                       // waits 15ms for the servo to reach the position
    }
  }
  servo->currentPosition = newPosition;
}

/*
  SerialEvent occurs whenever a new data comes in the hardware serial RX. This
  routine is run between each time loop() runs, so using delay inside loop can
  delay response. Multiple bytes of data may be available.
*/
void serialEvent() {
  while (Serial.available()) {
    byte incommmingByte = Serial.read();
    if (incommmingByte == endByte) {
      if(servoNumber == emptyValue || servo'state == emptyValue){
        //we have missed a crucial value, reset our ledger.
        resetSerialStorageValues();
      }else{
        //everything checks out, lets process the command
        stringComplete = true;
      }
    }else if(servoNumber == emptyValue){
      servoNumber = serialProcessBitMask & incommmingByte;
    }else if(servo'state == emptyValue){
      //maks the incomming byte and limit it to one bit (0 or 1);
      servo'state =  serialProcessBitMask & incommmingByte;
    }else{
      //something went wrong
      //continue by storing the values, but move them up a place
      servoNumber = servo'state;
      servo'state = incommmingByte;
    }
  }
}

void resetSerialStorageValues(){
    stringComplete = false;
    servoNumber = emptyValue;
    servo'state = emptyValue;
}

To calibrate each servo I wrote a simple servo controll code, that allows you to move (up to 6) servo arms from 0 to 180 degrees using the Serial monitor.
To move for example servo arm 1 (0x01) to 160(0xA0) degrees you send the following command over the Serial in HEX:

01 A0

The range between 0 and 180 is 0x00 till 0xB4.

/*
 * To calibrate each servo I wrote a simple servo controll code, that allows you to move (up to 6) servo arms from 0 to 180 degrees using the Serial monitor.  
* To move for example servo arm 1 (0x01) to 160(0xA0) degrees you send the following command over the Serial in HEX:
* 
"01 A0" 
The range between 0 and 180 is 0x00 till 0xB4.  
*
* @author: Joey van der Bie
* @date 2019-05-21
 * 
 */

#include <Servo.h>
Servo servo1, servo2, servo3, servo4, servo5, servo6;  // create servo object to control a servo

int servo'sTATE_OPEN = 1; //indicator for communication via serial
int servo'sTATE_CLOSED = 0; //indicator for communication via serial

//default positions (not normally used)
int servo'sTATE_OPEN_POSITION = 2;//degrees position for open
int servo'sTATE_CLOSED_POSITION = 95;// degrees position for closed


int ARDUINO_NUMBER = 1; //range 1,2,3 or 4 (Arduino position in servo)
int NUMBER_OF_servo's = 5; //number of servo's per Arduino;

//The servo structure
struct servo'struct {
  unsigned int  servoNumber: 5; // the number we addres from our serial communication, the :5 is the number of bits used
  unsigned int  pinNumber: 4; // the pin it is connected to, the :4 is the number of bits
  int   currentPosition; // current position in degrees
  Servo  servoObject; // the actual object
  int OPEN_POSITION; // open position (anything between 0 and 180)
  int CLOSED_POSITION;// closed position (anything between 0 and 180)
};

struct servo'struct *ss0, ss1, ss2, ss3, ss4, ss5, ss6;

void setup() {
  // initialize serial:
  Serial.begin(9600);

  ss1.servoNumber = (ARDUINO_NUMBER - 1) * NUMBER_OF_servo's + 1;
  ss1.pinNumber = 3;
  ss1.currentPosition = 0;
  ss1.OPEN_POSITION = servo'sTATE_OPEN_POSITION;
  ss1.CLOSED_POSITION = servo'sTATE_CLOSED_POSITION;
  ss1.servoObject = servo1;
  ss1.servoObject.attach(ss1.pinNumber);
  ss1.servoObject.write(ss1.OPEN_POSITION); // move the servo to its start position

  ss2.servoNumber = (ARDUINO_NUMBER - 1) * NUMBER_OF_servo's + 2;
  ss2.pinNumber = 5;
  ss2.currentPosition = 0;
  ss2.OPEN_POSITION = servo'sTATE_OPEN_POSITION;
  ss2.CLOSED_POSITION = servo'sTATE_CLOSED_POSITION;
  ss2.servoObject = servo2;
  ss2.servoObject.attach(ss2.pinNumber);
  ss2.servoObject.write(ss2.OPEN_POSITION);

  ss3.servoNumber = (ARDUINO_NUMBER - 1) * NUMBER_OF_servo's + 3;
  ss3.pinNumber = 6;
  ss3.currentPosition = 0;
  ss3.OPEN_POSITION = servo'sTATE_OPEN_POSITION;
  ss3.CLOSED_POSITION = servo'sTATE_CLOSED_POSITION;
  ss3.servoObject = servo3;
  ss3.servoObject.attach(ss3.pinNumber);
  ss3.servoObject.write(ss3.OPEN_POSITION);

  ss4.servoNumber = (ARDUINO_NUMBER - 1) * NUMBER_OF_servo's + 4;
  ss4.pinNumber = 10;
  ss4.currentPosition = 0;
  ss4.OPEN_POSITION = servo'sTATE_OPEN_POSITION;
  ss4.CLOSED_POSITION = servo'sTATE_CLOSED_POSITION;
  ss4.servoObject = servo4;
  ss4.servoObject.attach(ss4.pinNumber);
  ss4.servoObject.write(ss4.OPEN_POSITION);

  ss5.servoNumber = (ARDUINO_NUMBER - 1) * NUMBER_OF_servo's + 5;
  ss5.pinNumber = 9;
  ss5.currentPosition = 0;
  ss6.OPEN_POSITION = servo'sTATE_OPEN_POSITION;
  ss6.CLOSED_POSITION = servo'sTATE_CLOSED_POSITION;
  ss5.servoObject = servo5;
  ss5.servoObject.attach(ss5.pinNumber);
  ss5.servoObject.write(ss5.OPEN_POSITION);

  ss6.servoNumber = (ARDUINO_NUMBER - 1) * NUMBER_OF_servo's + 6;
  ss6.pinNumber = 11;
  ss6.currentPosition = 0;
  ss6.OPEN_POSITION = servo'sTATE_OPEN_POSITION;
  ss6.CLOSED_POSITION = servo'sTATE_CLOSED_POSITION;
  ss6.servoObject = servo6;
  ss6.servoObject.attach(ss6.pinNumber);
  ss6.servoObject.write(ss6.OPEN_POSITION);
}

void loop() {
  if ( Serial.available() >= 2 ) {
    if ((int)Serial.peek() <=  NUMBER_OF_servo's) {
      int servoNr = Serial.read();
      if (ss1.servoNumber == servoNr) {
        ss0 = &ss1;
      } else   if (ss2.servoNumber == servoNr) {
        ss0 = &ss2;
      } else   if (ss3.servoNumber == servoNr) {
        ss0 = &ss3;
      } else   if (ss4.servoNumber == servoNr) {
        ss0 = &ss4;
      } else   if (ss5.servoNumber == servoNr) {
        ss0 = &ss5;
      } else   if (ss6.servoNumber == servoNr) {
        ss0 = &ss6;
      } else {
        //Serial.print("No servo's found for number:");
        //Serial.println(servoNr, BIN);
        //Serial.println(ss1.servoNumber, BIN);
        //Serial.println(ss2.servoNumber, BIN);
        //Serial.println(ss3.servoNumber, BIN);
        //Serial.println(ss4.servoNumber, BIN);
        //Serial.println(ss5.servoNumber, BIN);
        //Serial.println(ss6.servoNumber, BIN);
      }
    }
    if ((int)Serial.peek() <= 180 ) {
      moveServo(ss0, Serial.read());
    }
  }
}

void moveServo(struct servo'struct *servo, int newPosition){
 servo->servoObject.write(newPosition); 
  servo->currentPosition = newPosition;
  Serial.println(servo->servoNumber);
  Serial.println(servo->currentPosition);
}

A board for an Arduino pro mini and 6 servo’s

For allowing the Arduino’s to controll the servo’s without using a breadboard I quickly designed a board that provided headers for the different cables to connect to.
The board provides a header for the FTDI connection, a header for the 6 PWM pins of the Arduino that are used for the servo’s, and a special power and ground pin, also to be used for the servo’s.

Because we wanted to use female headers, I had to use through hole components. I had difficulaties in selecting the propper through hole headers in KiCad, there were so many options, and all the options looked the same. I learnd from Anne that the difference is in the distance between the headers.
The distance between the headers is called the pitch.
This is indicated in the naming of the KiCad components with p followed by the distance in mm.

Milled holes end result

I made some errors with the board in my design, this can be found in the fails section. But after a few tries I managed to get them working.

Master controll Arduino

After having setup 4 Arduino pro mini’s that can controll the 20 servo’s, I started to work on a Arduino that will controll the Arduino pro mini’s. I reused the code I created for the Serial communication week to setup this arduino as the master.
Because of the simple data protocol I designed for the Arduino pro mini’s the master only has to send what servo needs to be open or closed. To move for example servo arm 1 (0x01) to open (0x01) you send the following command over the Serial in HEX:

01 01 FF

To close it you send:

01 00 FF

The 0xFF code is to indicate a message is finished.
So if the master wants to close all the servo’s it send the following data over the serial line:

01 00 FF
02 00 FF
03 00 FF
04 00 FF
05 00 FF
06 00 FF
07 00 FF
08 00 FF
09 00 FF
0A 00 FF
0B 00 FF
0C 00 FF
0D 00 FF
0E 00 FF
0F 00 FF
10 00 FF
11 00 FF
12 00 FF
13 00 FF
14 00 FF

When testing this code with the 20 servo’s we found that the marbles were getting stuck, they all wanted to leave their gate at about the same time.
To better regulate this we reversed the order of the servo’s, now the servo 20, next to the exit, starts and the first servo is the last to move.
Also to easier process the data in the Arduino, I stored it in a byte array:

byte allServosClosed[] =
{ 0x14, 0x00, 0xFF,
  0x13, 0x00, 0xFF,
  0x12, 0x00, 0xFF,
  0x11, 0x00, 0xFF,
  0x10, 0x00, 0xFF,
  0x0F, 0x00, 0xFF,
  0x0E, 0x00, 0xFF,
  0x0D, 0x00, 0xFF,
  0x0C, 0x00, 0xFF,
  0x0B, 0x00, 0xFF,
  0x0A, 0x00, 0xFF,
  0x09, 0x00, 0xFF,
  0x08, 0x00, 0xFF,
  0x07, 0x00, 0xFF,
  0x06, 0x00, 0xFF,
  0x05, 0x00, 0xFF,
  0x04, 0x00, 0xFF,
  0x03, 0x00, 0xFF,
  0x02, 0x00, 0xFF,
  0x01, 0x00, 0xFF
};

I also regulated the timing of sending the data, by adding a small delay after sending a individual servo message, we get a nice flow of opening and closing servo’s.

int delayBetweenServos = 200; //in millis

//send a servo message to the Arduino's. The *message is a indicator that you send a pointer to a byte array. 
//for more information about pointers visit: https://www.tutorialspoint.com/cprogramming/c_pointers.htm
void sendMessage(byte *message, int messageSize) {
  for (int i = 0; i < messageSize; i = i + 3) {
    mySerial.write(message[i]);
    mySerial.write(message[i + 1]);
    mySerial.write(message[i + 2]);
    delay(delayBetweenServos);
  }
}

Video showing the servo’s move in a nice sequence

Last Anne and Rutger made a really nice button panel, with 4 buttons indicating the different machine schedules we can display.

The Magic Marble Machine with the button panel

I attached an Arduino to the buttons, and wrote the code for the Arduino to send to all the other Arduino’s that are controlling the servo’s.
In the next itterations this Arduino will receive its code from the API.

Cable for Serial communication

It turned out we did not had enough headers for our Serial communication cable.
I solved this by cutting the cable and soldering in headers, making it possible to connect our 5 Arduino’s together.

Our FrankenStein-cable construction

Powering the stepper

For our first tests we powered our stepper motor using a regulated power supply.
Henk notified us, we were not allowed to use this device, but had to use standard power supplies.
I looked at the datasheet of the stepper.
The device is a SY42STH38-1684A.
In the datasheet we find it uses 2.8V and 1.68A
An Arduino uses something arount 0.4 A, so we might just be able to power the Arduino and stepper using a 5V 2A USB charger.
We tried it just before the presentation, and powering both the Arduino board and the stepper with a standard 5V USB power supply worked!

Specifications of the stepper Only 2 power supplies used for the whole machine!

API

The Waag Machine Scheduler is a booking system called “Booked Scheduler” or “PHPScheduleIT” made by Twinkle Toes Software.
The system is open source, and has an API. Only the API is not fully documented, but when we look at the code we can distill the API function calls.
On line 172 we find the function call “getAllReservations()”, which should give all reservations.
On line 206 we find the function call “getAllSchedules”, which probably gets all chedules of the system.
In the API configure file we find these function calls are mapped to a base URL: http://localhost/booked/Web/services/index.php
This URL is extended with a calls as: /Reservations/ for the “getAllReservations()” function call. This should result in a url as: http://localhost/booked/Web/services/Reservations/
The system can respond in JSON, making the date small and easy to process.

Fails

FTDI controller not recognised anymore

I connected first 4 servo’s to the microcontroller, using a lot of wires and the breadboard (sorry Neal) to supply the 5V and GND lines to all our devices ( 1 microcontroller + 4 servo’s).
Running the code controlling all the servo’s at once, showed perfomance was flacky at best.
Sometimes it all worked perfect, other times, not all servo’s moved the full 90 degrees.
There were even loops where the servo’s moved slower than intended.
Also after several minutes of trying my Macbook and Arduino environment started to be buggy.
In the Arduino environment the FTDI controller kept disapearing.
Only after disconnecting the FTDI controller from the computer and microcontroller and using a different USB port, it was recognised again by my computer.
This behavior woried me, since I was not even using 6 servo’s.

To ease the load, I modified the code to controll each servo in sequence for the movement from 0 to 90 degrees and all together from 90 to 0 degrees.
This allowed me better to compare the load and accuracy of the devices.

Servo’s buzzing

I noticed that some of my servo’s continued to buzz during their usage.
I played around with the movement, and found that some servo’s have trouble moving to the correct position.
Also there were servo’s that were not confortable with moving to the 0 degrees position.
I adjusted the code to move to 2 degrees as the start position instead of 0.
This resolved most of the buzzing. I had one servo left that kept buzzing no matter what I tried.
The poor thing just could not manage to move to the correct position. I marked the servo and replaced it for one that did not buzz.

Endbit using “\n” (0x0A) instead of 0xFF

Because it was easy using the Arduino terminal and also used in the serial example I used, I used the end of line character “\n” to indicate the end of the datapakket.
I normally use a full byte 0xFF for this, because it is easier to distinquis.
This turned out to be now also the case.
When trying to expand the servo’s further than number 9, I ran into problems. This because I used the byte values to indicate the servo numbers, not the ASCII characters.
My hybrid version of ASCII and byte values made it all really confusing.
I decided to stick with byte values and exchanged the “\n” for 0xFF. This made everything work as expected.
It did ment I could not use the Arduino Serial terminal anymore as you normally would, since it did not support sending raw values. I needed to select my characters in such a way to resemble the raw byte values.
I switched to CoolTerm, an application that does allow me to send raw byte values (in hexadecimal format). So sending a message to switch servo number 20 (decimal) to position 1 would look like: 14 01 FF
0x14 = DEC 20
0x01 = DEC 1
0xFF = DEC 255

The full list can be found here CoolTerm in action

Through-hole milling

For our servo mounting board we wanted to use female headers to plug the Arduino pro mini’s on the board.
We only had through-hole female headers available, meaning we had to mill the holes for these headers as well.
In the output week I tried to mill holes for my vibration motor, I was not succesfull in milling the correct size.
Now I wanted to try a different approach. I though the holes milling pattern should be hidden in one layer in KiCAD somewhere.
I could not find it when looking at the layers in the software. I exported all layers and looked at the indidivual exports (thanks Anne for the tip). I found a layer that showed the headers, converted the SVG to PNG to use for milling.

The layer showing the headers from the design, whatch the squares

The layer showed not only round headers, but also square headers, since I did not wanted to mill square headers, I dedited them to circles.

Edited version of the headers without squares

Big mistake, I should have realized I was looking at the solder paste mask layer, not a layer to use for milling.
Fortunate Rutger pointed me towards this problem before we started milling. Thanks Rutger!
If we did stared milling with this file, we would not be able to solder the headers.

I continued to look at a correct layer, but did not found one that was fully compatible. The most closely matching layer I found was the B copper layer.
It showed the headers with the holes, but not fully in the correct colors.

Copper B-layer, almost usable for milling the holes

I figured I could easily fix this is I colored the background black, this would leave only the holes as white.
I inserted a background black square in the top of the SVG file and exported the file to PNG:

<rect width="100%" height="100%" fill="#000000"/>

Edited B-layer to only show the holes

Still the holes were to big when milling, but after inverting I had the correct hole size.

Inverted holes file for milling

Milled holes end result

For the screw holes, I also had to edit the SVG file.
The holes were not filled, making it undetectable for the mods software.

Holes to small to solder

When trying to solder the headers to the board, I had difficulties getting the solder to flow through the holes.
I tried different approaches:

  • adding a little bit of solder to the legs of the headers before inserting them in the holes (this made it very difficult to put them in)
  • adding a little bit of solder to the pads around the legs (this made it almost impossible to fit the header trough and I broke one coper trace) I managed to solder about half the legs to the pads. After half an hour of trying to solder and wiggle the headers a littlebit everything went wrong, the traces broke, and I ruined the board. I decided to try to mill a new board, but make the holes a littlebit larger, by inverting the colors.

Also I kept on thrying with my ruined board, but now adding solder to the part where the headers need to be mounted to the pads, then inserting the header and heating the pins and adding a little bit more solder.
This worked, but the proces is not ideal.

Connected 13 and 12 instead of 10 and 11

In my first board design I connected pin 13 ad 12 instead of 10 and 11 for the servo 5 and 6.
Since 13 and 12 do not have PWM, I redesigned my board to use the correct pins.
For the milled boards I used wired to bridge the correct pins.

Boards keep breaking

Somehow the servo board I designed keeps failing.
The copper traces are maybe to thin, while they are the same dimensions are they were with my previous boards, 0.4 mm.
I am suspecting this has to do with the headers putting more strain on the traces than the SMD components.
This also explains why the headers at Rutger his boars keeps breaking of and destroying his traces.
Overall my experience with the newest milled boards is that they are as solid as using breadboards, only taking way more time to produce… I am open for suggestions in how to make the traces more solid, maybe a special coating?

Send the wrong files to mill

For the traces I redesigned my servo board.
Only for milling the new boards I accidentally send the wrong files to Rutger to mill.
This resulted in having us milled 4 old boards, instead of 2 new and 2 old.

Arduino unresponsive

To quicken the soldering of the servo boards, the team soldered an arduino board directly to the servo board.
Unfortunately something went wrong with the soldering, or it was a faulty Arduino.
I tried rechecking and resoldering all the connectiongs, but the board stayed unresponsive.
In the end we had to discard it and mill a new board.
This really shows the value in having a prorotyping board where you can easily stick a microcontroller on.
With the other boards we did not had this issue, since for these boards we used the headers and cold replace the Arduino when it was unresponsive.

Reflection

Marble machines are great, and the team was great!
It was a long time that I had worked with such a dedicated team on a fun and beatifull project!
I am really proud on what we acheaved as a team in such a short time withou losing our marbles!!
Thanks team: Rutger, Anne, Micky! (and Henk and the crew of the Waag for the tips!)




Share this story