accelerating the transition to distributed manufacturing

Automatic door opener

  • YouTube

The Code 

To manage the interactions of the touchless door, we used an state dependant program. That means that the programs acts different depending on the current state of the door. It features five states:

  • Closed(default)

  • Opening from the pull side

  • Opening from the push side

  • Fully opened

  • Closing

Then, to manage the inputs and outputs of the program we use the following functions:

For the ultrasonic sensors, we read their value at the start of the loop() function

distance1 = getDistance(TRIG1, ECO1);

distance2 = getDistance(TRIG2, ECO2);

The getDistance function is our standard ultrasonic sensor handler.

int getDistance(int TRIG, int ECO){

digitalWrite(TRIG, LOW);

delayMicroseconds(2);

digitalWrite(TRIG, HIGH);

delayMicroseconds(10);

digitalWrite(TRIG, LOW);

int duration = pulseIn(ECO, HIGH); return duration / 58.2;

}

Then we handle the DC motor(the one that moves the door) with the doorMotor() function. This is just a method to use the motor in both directions and stop it, using the l298n driver.

#define MOTOR_CLOSING -1

#define MOTOR_STOPPED 0

#define MOTOR_OPENING 1

int IN1 = 8;

int IN2 = 9;

int ENA = 10;

void doorMotor(int rotDirection){

   if(rotDirection == MOTOR_CLOSING){

      Serial.println ("Door motor closing...");

      digitalWrite(ENA, HIGH);

      digitalWrite(IN1, HIGH);

      digitalWrite(IN2, LOW);

    }else{

   if(rotDirection == MOTOR_STOPPED){

     Serial.println ("Door motor stopping...");

     digitalWrite(ENA, LOW);

     digitalWrite(IN1, LOW);

     digitalWrite(IN2, LOW);

   }else{

  if(rotDirection == MOTOR_OPENING){

    Serial.println ("Door motor opening...");

    digitalWrite(ENA, HIGH);

    digitalWrite(IN1, LOW);

    digitalWrite(IN2, HIGH);

      }

     }

    }

  }

And last but not least we move the handle with an stepper motor. We control it with a lookup table. It controls the coils of the stepper which are activated in a certain order.

const int STEP1 = 4;

const int STEP2 = 5;

const int STEP3 = 6;

const int STEP4 = 7;

const int numSteps = 4;

const int stepsLookup[4] = {B1000, B0100, B0010, B0001};

As you can see by default we use the stepper with one-phase behaviour as we got the best results this way but it can be changed.

Then to advance the motor we use the opening() and closing() functions which are very similar.

void opening(){

   stepCounter++;

   if(stepCounter >= numSteps) stepCounter = 0;

   setOutput(stepCounter);

}

void closing(){

   stepCounter--;

   if(stepCounter < 0) stepCounter = numSteps - 1;

   setOutput(stepCounter);

}

We also included a demagnetize() function to turn off all coils.

void demagnetize(){

digitalWrite(STEP1, LOW);

digitalWrite(STEP2, LOW);

digitalWrite(STEP3, LOW);

digitalWrite(STEP4, LOW);

}

And finally the setOutput function translates these look-up table values to the coils that should be activated in the stepper.

void setOutput(int step){

digitalWrite(STEP1, bitRead(stepsLookup[step], 0));

digitalWrite(STEP2, bitRead(stepsLookup[step], 1));

digitalWrite(STEP3, bitRead(stepsLookup[step], 2));

digitalWrite(STEP4, bitRead(stepsLookup[step], 3));

}

Now that we know how to read the sensors and activate the motors we can start explaining the behaviour of the program.

To behave differently in each state, we use a switch clause in out loop() function, governed by an integer that represents the state of the program.

#define DOOR_CLOSED 0

#define DOOR_OPENING 1

#define DOOR_OPENING_OUTSIDE 4

#define DOOR_OPENED 2

#define DOOR_CLOSING 3

int doorState = DOOR_CLOSED;

void loop(){

      switch(doorState){

        case DOOR_CLOSED:

                  ...

        break;

        ...  

        case DOOR_OPENED:

        ...

        break;

      }

 }

Now we will examine the behaviour of the program in each state.

 

Closed

When the door is closed we want the program to wait until the user places his hand on the ultrasonic sensor, so we compare the value read by the sensor with a constant that defines how close should the user place his hand

const int handDistance = 5;  // trigger distance set to 5cm

We make sure that the distance is positive as the sensor can sometimes give erratic values. We will expand on this problem later. This is the code for detecting the user's hand in the sensor that is on the pull side.

case DOOR_CLOSED:

             if(distance1 <= handDistance && distance1 > 0){

When we detect that the user has placed his hand on the sensor, we start turning the stepper motor, controlling the door's handle.

for(int i = 0; i < steps; i++){

            opening();

            delayMicroseconds(motorSpeed);

   }

We can tweak the angle of rotation by modifying the variable steps.

When the handle is opened, we stop sending current to the stepper coils so the rest of the components can draw more power(this is specially important for the motor that opens the door)

demagnetize();

Then we can start the DC motor that opens the door and change the program's state to opening

doorState = DOOR_OPENING;

doorMotor(MOTOR_OPENING);

handInSensor = true;

We also set the variable handInSensor to True, for the reasons we will see in the next chapter.

 

 

 

Opening

When the door is opening, we don't have to do anything until it fully opens, when we should stop the DC motor. So we compare the value of the inside sensor with a constant that determines how close to the wall do we want the door to stop.

const int wallDistance = 20;

case DOOR_OPENING:

if(distance1 <= wallDistance && distance1 > 0 && !handInSensor){

As you can see we use the variable handInSensor in here. The reasoning is simple, if we didn't use it as soon as the door started opening it would detect the hand of the user still on the sensor as if it were the wall, and the motor would stop. So we first have to detect when the user takes away his hand to avoid this problem.

if(distance1 > wallDistance){

handInSensor = false;

}

So when the hand is taken away and the sensor detects proximity to an object, we stop the DC motor and set the state to fully opened.

doorMotor(MOTOR_STOPPED);

doorState = DOOR_OPENED;

 

 

 

Opening from the outside

You may have noticed that we have only explained how to open the door from the pull side. To open it from the push side we do basically the same, with the exception that we don't have to use that handInSensor variable, as the sensor that detected the hand and the sensor that will detect the wall are different.

case DOOR_CLOSED:

     if(distance2 <= handDistance && distance2 > 0){

            for(int i = 0; i < steps; i++){

              opening();

              delayMicroseconds(motorSpeed);

            }

           demagnetize();

           doorState = DOOR_OPENING_OUTSIDE;

           doorMotor(MOTOR_OPENING);

       }

break;

Then, when it is opening;

case DOOR_OPENING_OUTSIDE:

        if(distance1 <= wallDistance && distance1 > 0){

        Serial.println("Wall");

        doorMotor(MOTOR_STOPPED);

        doorState = DOOR_OPENED;

        }

break;

 

 

Opened

When the door has opened we need to wait some time to let the user pass through the door and then start to close it, so we use a delay with another constant, that represents the waiting time in milliseconds.

const int doorWaitTime = 2000;

case DOOR_OPENED:

        delay(doorWaitTime);

        doorMotor(MOTOR_CLOSING);

        doorState = DOOR_CLOSING;

break;

 

 

 

Closing

So here comes the difficult part. In order to not introduce more components to the project, we decided to detect when the door is closed by reading the push side sensor value and detecting when it stays constant(indicating the door has stopped moving)

To do this, we tried to take samples of the readings of the sensor and calculating its variance. We do this at the start of the loop function, having declared the size of the sample and the variables we will use.

const int sampleSize = 3;

const float varThreshold = 1.5;

int sampleIndex = 0;

int sample[sampleSize];

float sampleVariance = 0;

So in each iteration of the loop() function we save the sensor's detected value in our sample.

 

sample[sampleIndex] = distance2; sampleIndex++;

Then, when we have collected a full sample, we detect its variance

if(sampleIndex % sampleSize == 0){

    sampleIndex = 0;

    int mean = 0;

    for(int i = 0; i < sampleSize; i++){

    mean += sample[i];

    }

    mean /= sampleSize;

    sampleVariance = 0;

    for(int i = 0; i < sampleSize; i++){

         sampleVariance += pow(sample[i] - mean, 2);

     }

sampleVariance /= sampleSize;

}

So finally we determine the door has stopped if the variance of the last sample is less than the constant varThreshold we defined earlier. In out switch statement,

case DOOR_CLOSING:

    if(sampleVariance < varThreshold){

      doorState = DOOR_CLOSED;

      doorMotor(MOTOR_STOPPED);

      for(int i = 0; i < steps; i++){

       closing();

       delayMicroseconds(motorSpeed);

      }

      demagnetize();

    }

break;

As you can see we first make sure the door is closed and then we return the handle to the original position. This is because our DC motor doesn't have enought torque to close the door with the handle closed. If you are using a more powerful motor, you can close the handle when the door is on the opened position.

If the sensor gave accurate values we would have finished, but this is not the case. When the sensor is a long distance from an object it gives erratic values. We noticed they tend to be the correct value but sometimes drop a large amount. This in turn creates a large variance even when the door is stationary, making this code useless.

To solve this problem, we take a sub-sample of the sensor values and take the biggest value of the sample as the real sensor reading. We do this in a similar way to the previous sampling. First we define our variables:

const int subSampleSize = 5;

int subSampleIndex = 0;

float subSampleMax = 0;

And then at the start of the loop() function we compute this sub-sample maximum,

if(distance2 > subSampleMax){

         subSampleMax = distance2;

        }

subSampleIndex++;

When the sub-sample is finished, we feed its value to the original sample, as it if were the sensor reading, and reset the variables to take a new sub-sample.

if(subSampleIndex % subSampleSize == 0){

    sample[sampleIndex] = subSampleMax;

    sampleIndex++;

    subSampleMax = 0;

    subSampleIndex = 0;

}

Then we change a bit the big sample code so it only computes the variance one time.

if(sampleIndex % sampleSize == 0 && subSampleIndex % subSampleSize == 0){

    ...

}

Here we can see the effect of the sub-sample, how it filters out these spikes and the general noise. Blue is the raw sensor data, red is the subSampleMax value.

And below the green value is the variance. You can see in the graph that the value is pretty high when the door is closing as the sensor values move a lot but when it is closed it drops to almost zero as the sensor values are mostly constant.

By adjusting the sample and sub-sample sizes we tried to find a compromise between having small samples which means more noise and in turn less precision(stopping before closing completely for example) and big samples which means slower detection, so the DC motor keeps running a bit with the door is already closed.

Schema 

 
 

info@3dthinks.com

Tel. +49 8424 7939017

Captura%2520de%2520Pantalla%25202020-07-

3dthinks UG

Gartenstr. 27

85128 Nassenfels

Germany

Amstgericht Ingolstadt HBR 9578

USt-IdNr. DE322890122

  • Facebook
  • Twitter
  • YouTube
  • Pinterest
  • Tumblr Social Icon
  • Instagram