Skip to content

Machine building week

For this week we needed to build a machine. We started brainstorming and came up with a few ideas.

alt text Here are a few of our ideas

  • In the top left we have the self driving trash can that comes to pick up your trash.
  • An automatic tea maker
  • Some kind of machine that keeps on juggling something
  • An introverted shy robot
  • A machine that automatically shoots darts in the bullseye of a dart board
  • An system to automatically refills the water in our coffee machines so it doesn't starts beeping loudly

We went with the idea of the Shy robot in the end. I thought the dart throwing robot was really cool. But we went with the Shy robot in the end because it is more realistic to make and democracy.

alt text

Here are the first few sketches of the shy robot. We wanted to give it eyes on strings that push but we wanted more mechanical stuff. So we decided on making his head a big lid and making the eyes pop out there. So we used more actuators and mechanical stuff.

alt text

alt text During our brainstorming session Henk also asked us to think about which sensor you are going to use and how many to see how many pins you need.

Starting with the electronics

Starting the project we used last years prototype as our starting point. alt text (Our robot already strapped with an Ultrasonic and a Time of flight sensor) Dylan and I wanted to do the coding and electronics of the robot and Patrick and Irja took the designing and building part of the robot.

We realized quickly that we only had 2 ports left over because all other where in use by the arduino motor drive hat. These 2 ports that where left over where the I2C port SDA and SCL. alt text

So I thought let's use ToF sensors since you can hook up multiple on I2C. After some digging in the datasheet I found out that wouldn't work because whenever you assign a I2C address to a VL53X1L it would forget it next power cycle. So I thought: what if I used a multiplexer connected to all the XSHUT pins of the ToF sensor to turn them on one by one and assign them each their own I2C address on startup. alt text

Luckily we had the I2C multiplexer PA9555. So I used I2C for everything because I had limited pins. So I started designing a circuit.

alt text With this board I wanted to make a breakout for 5 I2C devices. So I could hook up 5 time of flight sensors for example. The first attempt I gave up. I also asked henk for some feedback on how to do the connectors. He recommended using UDPI connectors and making breakout boards for every ToF sensor so I could connect them properly.

alt text So my schematic wen't to this. alt text make because of all the bridges I needed to add in

alt text This is the most recent design for now. It was a pain to make all the bridges and swapping back and forth.

This is the design for the breakout board. We are using pin headers since we already have soldered ToF sensors alt text alt text alt text

The end result:

alt text This board could connect 5 ToF sensors together on one I2C bus.

Chapter 2 Ultrasonic sensors

We didn't have enough ToF sensors for our robot. I asked Henk if I could order some for myself and use them in the project. But he told me no and to use ultrasonic sensors. And he told me to use the current board for that. But I said that wouldn't work because only one multiplexer pin goes to each breakout because I designed it for the ToF so now im going to do some solder hacking and try to wire it together myself.

Failure
int readUltrasonicSensor(int trig, int echo)
{
long duration, cm;

// The sensor is triggered by a HIGH pulse of 10 or more microseconds.
// Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
ioex.write(PCA95x5::Port::P06, PCA95x5::Level::L);
delayMicroseconds(5);
ioex.write(PCA95x5::Port::P06, PCA95x5::Level::H);
delayMicroseconds(10);
ioex.write(PCA95x5::Port::P06, PCA95x5::Level::L);

ioex.direction(PCA95x5::Port::P07, PCA95x5::Direction::IN);
duration = pulseIn(ioex.read(PCA95x5::Port::P07), HIGH);
// Convert the time into a distance
cm = (duration / 2) / 29.1; // Divide by 29.1 or multiply by 0.0343
Serial.print("Distance: ");
Serial.print(cm);
return (cm);
// https://randomnerdtutorials.com/complete-guide-for-ultrasonic-sensor-hc-sr04/
}

Here I tried measuring the signal myself but I didn't receive anything back from the multiplexer

Failure
// A0 trigger, A1 Echo
int readUltrasonicSensor(int trig, int echo)
{

long duration, cm;

// The sensor is triggered by a HIGH pulse of 10 or more microseconds.
// Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
ioex.write(PCA95x5::Port::P06, PCA95x5::Level::L);
delayMicroseconds(5);
ioex.write(PCA95x5::Port::P06, PCA95x5::Level::H);
delayMicroseconds(10);
long startTime = micros();
ioex.write(PCA95x5::Port::P06, PCA95x5::Level::L);

while (PCA95x5::Level::H == ioex.read(PCA95x5::Port::P07))
{
    duration = micros() - startTime;
}

// Convert the time into a distance
cm = (duration / 2) / 29.1; // Divide by 29.1 or multiply by 0.0343
Serial.print("Distance: ");
Serial.print(cm);
    return (cm);
// https://randomnerdtutorials.com/complete-guide-for-ultrasonic-sensor-hc-sr04/
}

Sort of works but it works super innacurate. I can move it around and it will display different values depending on how far it is from the table

alt text

For now I will plug the trigger into the multiplexer and the Echo pin into the arduino. Then I can measure the length of the signal to measure the distance.

Optimization for the stepper control

We had the issue that the arduino didn't run fast enough to read from the sensors and to control the motors simultaneously even when shrinking down the code to this.

Old
#include <AccelStepper.h>

//=============================================================================================//
//Stepper motor pins
//=============================================================================================//

#define MOTOR_X_ENABLE_PIN 8
#define MOTOR_X_STEP_PIN 2
#define MOTOR_X_DIR_PIN 5


#define MOTOR_Y_ENABLE_PIN 8
#define MOTOR_Y_STEP_PIN 3
#define MOTOR_Y_DIR_PIN 6


#define MOTOR_Z_ENABLE_PIN 8
#define MOTOR_Z_STEP_PIN 4
#define MOTOR_Z_DIR_PIN 7

#define MOTOR_A_ENABLE_PIN 8
#define MOTOR_A_STEP_PIN 12
#define MOTOR_A_DIR_PIN 13

//=============================================================================================//
//Global object creations
//=============================================================================================//

AccelStepper motorX(1, MOTOR_X_STEP_PIN, MOTOR_X_DIR_PIN);
AccelStepper motorY(1, MOTOR_Y_STEP_PIN, MOTOR_Y_DIR_PIN);
AccelStepper motorZ(1, MOTOR_Z_STEP_PIN, MOTOR_Z_DIR_PIN);
AccelStepper motorA(1, MOTOR_A_STEP_PIN, MOTOR_A_DIR_PIN);

//=============================================================================================//
// Function declarations
//=============================================================================================//

void moveForw(int speed);
void moveBack(int speed);
void moveStop();
int readUltrasonicSensor(int trigPin, int echoPin);
void rotateLeft();
void stepperSetup();

int mm = 0;

void setup()
{
Serial.begin(9600);
stepperSetup();
pinMode(A2, INPUT);
}

void loop()
{
mm = readUltrasonicSensor(A1, A0) * 10; // Convert cm to mm
Serial.println(mm); // Print distance in mm
// Define distance thresholds
const int SAFE_DISTANCE_FRONT = 2500;    // 500mm
const int WARNING_DISTANCE_FRONT = 1250; // 250mm
const int DANGER_DISTANCE_FRONT = 800;   // 100mm

const int SAFE_DISTANCE_BACK = 400;   // 40cm
const int DANGER_DISTANCE_BACK = 200; // 20cm

moveForw(0);
}

void moveForw(int stepDelay)
{
motorX.move(3000);
motorX.run();
motorY.move(3000);
motorY.run();
motorZ.move(3000);
motorZ.run();
motorA.move(3000);
motorA.run();

}

void moveBack(int speed)
{
motorX.move(-3000);
motorX.run();
motorY.move(-3000);
motorY.run();
motorZ.move(-3000);
motorZ.run();
motorA.move(-3000);
motorA.run();
}

void moveStop()
{
motorX.move(0);
motorX.run();
motorY.move(0);
motorY.run();
motorZ.move(0);
motorZ.run();
motorA.move(0);
motorA.run();
}

void rotateLeft()
{
motorX.move(3000);
motorX.run();
motorY.move(1500);
motorY.run();
motorZ.move(1500);
motorZ.run();
motorA.move(1500);
motorA.run();

}

// A0 trigger, A1 Echo
int readUltrasonicSensor(int trigPin, int echoPin) {
int duration, cm;
// The sensor is triggered by a HIGH pulse of 10 or more microseconds.
// Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
digitalWrite(trigPin, LOW);
delayMicroseconds(5);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);

// Read the signal from the sensor: a HIGH pulse whose
// duration is the time (in microseconds) from the sending
// of the ping to the reception of its echo off of an object.
pinMode(echoPin, INPUT);
duration = pulseIn(echoPin, HIGH);

// Convert the time into a distance
cm = (duration/2) / 29.1;     // Divide by 29.1 or multiply by 0.0343


return cm;
}

void stepperSetup(){
pinMode(MOTOR_X_ENABLE_PIN, OUTPUT);
pinMode(MOTOR_Y_ENABLE_PIN, OUTPUT);
pinMode(MOTOR_Z_ENABLE_PIN, OUTPUT);
pinMode(MOTOR_A_ENABLE_PIN, OUTPUT);

motorX.setEnablePin(MOTOR_X_ENABLE_PIN);
motorX.setPinsInverted(false, false, true);
motorX.setAcceleration(100);
motorX.setMaxSpeed(100);
motorX.setSpeed(100);
motorX.enableOutputs();

motorY.setEnablePin(MOTOR_Y_ENABLE_PIN);
motorY.setPinsInverted(false, false, true);
motorY.setAcceleration(100);
motorY.setMaxSpeed(100);
motorY.setSpeed(100);
motorY.enableOutputs();

motorZ.setEnablePin(MOTOR_Z_ENABLE_PIN);
motorZ.setPinsInverted(false, false, true);
motorZ.setAcceleration(100);
motorZ.setMaxSpeed(100);
motorZ.setSpeed(100);
motorZ.enableOutputs();

motorA.setEnablePin(MOTOR_Z_ENABLE_PIN);
motorA.setPinsInverted(false, false, true);
motorA.setAcceleration(100);
motorA.setMaxSpeed(100);
motorA.setSpeed(100);
motorA.enableOutputs();
}

We removed everything down to the ultrasonic sensor readings and only then the motors could turn well. So I asked henk to help figure out a solution. The solution we came up with is a secondary MCU connected to the arduino and then send commands over serial to the arduino to run on the motors.

So the main MCU would do all the thinking and readings from the sensors and the arduino will now act as a stepper motor controller. This is the code currently for the arduino.

Code
/*
This example shows how to take simple range measurements with the VL53L1X. The
range readings are in units of mm.
*/
#include <AccelStepper.h>

//=============================================================================================//
// Stepper motor pins
//=============================================================================================//

#define MOTOR_X_ENABLE_PIN 8
#define MOTOR_X_STEP_PIN 2
#define MOTOR_X_DIR_PIN 5

#define MOTOR_Y_ENABLE_PIN 8
#define MOTOR_Y_STEP_PIN 3
#define MOTOR_Y_DIR_PIN 6

#define MOTOR_Z_ENABLE_PIN 8
#define MOTOR_Z_STEP_PIN 4
#define MOTOR_Z_DIR_PIN 7

#define MOTOR_A_ENABLE_PIN 8
#define MOTOR_A_STEP_PIN 12
#define MOTOR_A_DIR_PIN 13

//=============================================================================================//
// Global object creations
//=============================================================================================//

AccelStepper motorX(1, MOTOR_X_STEP_PIN, MOTOR_X_DIR_PIN);
AccelStepper motorY(1, MOTOR_Y_STEP_PIN, MOTOR_Y_DIR_PIN);
AccelStepper motorZ(1, MOTOR_Z_STEP_PIN, MOTOR_Z_DIR_PIN);
AccelStepper motorA(1, MOTOR_A_STEP_PIN, MOTOR_A_DIR_PIN);

//=============================================================================================//
// Function declarations
//=============================================================================================//

void stepperSetup();
void processCommand(String command);
String receivedMessage = "";

void setup()
{
  Serial.begin(115200);
  stepperSetup();

}

void loop()
{
  // Process any available serial data
  while (Serial.available())
  {
    char incomingChar = Serial.read();

    if (incomingChar == '\n')
    {
      processCommand(receivedMessage);
      Serial.println(receivedMessage);
      receivedMessage = "";
    }
    else
    {
      // Add the character to the message until the message is complete
      receivedMessage += incomingChar;
    }
  }

  // Run the motors (non-blocking)
  motorX.run();
  motorY.run();
  motorZ.run();
  motorA.run();
}

// Process incoming commands in format "motor#,speed"
void processCommand(String command)
{
  // Find the comma position
  int commaIndex = command.indexOf(',');

  // Make sure the command has a comma
  if (commaIndex < 0)
  {
    receivedMessage = "";
    return;
  }

  // Seperate the motor number and speed value
  int motorNum = command.substring(0, commaIndex).toInt();
  int motorSpeed = command.substring(commaIndex + 1).toInt();

  // number 1-4 for motors
  switch (motorNum)
  {
  case 1:
    motorX.setSpeed(motorSpeed);
    if (motorSpeed == 0)
    {
      motorX.stop();
    }
    else
    {
      motorX.move(motorSpeed * 100); // just keep moving until told otherwise and the * 100 is so it rotates the correct direction
      motorX.setMaxSpeed(motorSpeed);
    }
    break;

  case 2:
    motorY.setSpeed(motorSpeed);
    if (motorSpeed == 0)
    {
      motorY.stop();
    }
    else
    {
      motorY.move(motorSpeed * 100);
      motorY.setMaxSpeed(motorSpeed);
    }
    break;

  case 3:
    motorZ.setSpeed(motorSpeed);
    if (motorSpeed == 0)
    {
      motorZ.stop();
    }
    else
    {
      motorZ.move(motorSpeed * 100);
      motorZ.setMaxSpeed(motorSpeed);
    }
    break;

  case 4:
    motorA.setSpeed(motorSpeed);
    if (motorSpeed == 0)
    {
      motorA.stop();
    }
    else
    {
      motorA.move(motorSpeed * 100);
      motorA.setMaxSpeed(motorSpeed);
    }
    break;

  default:
    break;
  }
}

void stepperSetup()
{
  pinMode(MOTOR_X_ENABLE_PIN, OUTPUT);
  pinMode(MOTOR_Y_ENABLE_PIN, OUTPUT);
  pinMode(MOTOR_Z_ENABLE_PIN, OUTPUT);
  pinMode(MOTOR_A_ENABLE_PIN, OUTPUT);

  motorX.setEnablePin(MOTOR_X_ENABLE_PIN);
  motorX.setPinsInverted(false, false, true);
  motorX.setAcceleration(800);
  motorX.setMaxSpeed(800);
  motorX.setSpeed(800);
  motorX.enableOutputs();

  motorY.setEnablePin(MOTOR_Y_ENABLE_PIN);
  motorY.setPinsInverted(false, false, true);
  motorY.setAcceleration(800);
  motorY.setMaxSpeed(800);
  motorY.setSpeed(800);
  motorY.enableOutputs();

  motorZ.setEnablePin(MOTOR_Z_ENABLE_PIN);
  motorZ.setPinsInverted(false, false, true);
  motorZ.setAcceleration(800);
  motorZ.setMaxSpeed(800);
  motorZ.setSpeed(800);
  motorZ.enableOutputs();

  motorA.setEnablePin(MOTOR_Z_ENABLE_PIN);
  motorA.setPinsInverted(false, false, true);
  motorA.setAcceleration(800);
  motorA.setMaxSpeed(800);
  motorA.setSpeed(800);
  motorA.enableOutputs();
}

So now I could send commands to the motors over Serial and see the motors spin in realtime. The command is structured like this. 1,500 Where 1 is the motor number and 500 is the speed of the motor. So like this I could control every motor separately.

Using an ESPC6

Since we couldn't use the Arduino for any logic stuff we needed something else to talk to the arduino that's where the ESP came in to play. I only attached the Esp Tx to the Rx of the arduino because these 2 mcu's work on different logic levels and I was concerned that the arduino may fry the the ESP if it send anything back.
//TODO: add more docs

Getting ultrasonic sensors to work (Again)

On the last day I tried to connect everything together. I first started out with one ultrasonic sensor but I noticed I wasn't getting any data from it. So I started debugging and only running the code necessary to get the sensor running but that also didn't work

Code
int trigPin = D4;    // Trigger
int echoPin = D9;    // Echo
long duration, cm, inches;

void setup() {
  //Serial Port begin
  Serial.begin(9600);
  //Define inputs and outputs
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
}

void loop() {
  // The sensor is triggered by a HIGH pulse of 10 or more microseconds.
  // Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
  digitalWrite(trigPin, LOW);
  delayMicroseconds(5);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);

  // Read the signal from the sensor: a HIGH pulse whose
  // duration is the time (in microseconds) from the sending
  // of the ping to the reception of its echo off of an object.
  duration = pulseIn(echoPin, HIGH);

  // Convert the time into a distance
  cm = (duration/2) / 29.1;     // Divide by 29.1 or multiply by 0.0343
  inches = (duration/2) / 74;   // Divide by 74 or multiply by 0.0135

  Serial.print(inches);
  Serial.print("in, ");
  Serial.print(cm);
  Serial.print("cm");
  Serial.println();

  delay(250);
}

I double, triple checked every connection and grabbed new wires in case that was the issue. After a while a directly connected it to the mcu but that also didn't work and then I gave up.

alt text

After going to Henk and reading the datasheet he said I needed to use 5 volts instead of 3.3 volts When I changed that connection it worked.