Ron Swanson Door Slammer

Context

This project was inspired by the device featured on the NBC show Parks and Recreation, in which a surly office manager has a device installed to remotely slam his door on annoying co-workers.

The link to the video of my implementation is below. The 12V motor proved to be too small to slam a house door effectively, so the device was moved to the dog crate and used on that.

Physical Layout

The Board

The board consists of an LCD Text display, an H-Bridge for the motor, an IR receiver and one power supply input.

/media/uploads/mversteeg3/_scaled_slammer.jpg

LCD connections

Pin numberTextLCD pinsmbed pins
1GND0V
2VCC9V, via 10k resistor
3VO0V, via 1k resistor
4RSp23
5RW0V
6Ep24
7D0not connected
8D1not connected
9D2not connected
10D3not connected
11D4p25
12D5p26
13D6p27
14D7p28

IR Sensor

MBEDIR
VoutVCC
p15OUT
GNDGND

Force Sensors

Wall sensor

MBEDForce Sensor
VoutPower
p16Ain
GNDGND

Door Sensor

MBEDForce Sensor
VoutPower
p20Ain
GNDGND

The Motor

The motor to close the door consists of a 12V DC motor with a C-Clamp locked onto the drive shaft. There are two pens reinforced by duct tape that form the "arm" of the device. At the end of this arm, there are two force sensors that are used to detect the force exerted by the arm on the door or wall.

/media/uploads/mversteeg3/_scaled_slammer_mod.jpg

Connections with H-Bridge

MBEDHBridge
Vu+5V
GNDGND
revIN_B
fwdIN_A
pull-upEN_A
pull-upEN_B
pwmPWM

Connect the OUT_A and OUT_B to the two Motor wires and the power supply wires to their respective motor supply lines. Ensure the polarities are not switched!

Logical Design

Variables and Constants

  • Variables
    • float pushSpeed
      • Specifies “pushing” speed of the motor, that is the speed setting while in contact with the door.
    • float forceThresh
      • The amount of force read by the force sensor that will cause the device to stop closing the door and return to base state.
    • bool held
      • Boolean value used to ensure the device does not register a cancel signal when the user holds the button down.
    • float speed
      • The speed to set the motor to. Will be either pushSpeed, COAST_SPEED, 0, or REVERSE_SPEED.
  • Constants
    • SPEED_INC
      • The amount to increment pushSpeed up/down when volume up/down is pressed.
    • FORCE_INC
      • The amount to increment forceThresh
    • COAST_SPEED
      • Speed of the motor when not in contact with the door
    • SPEED_MIN
      • Minimum pushing speed
    • REVERSE_SPEED
      • Speed in reverse while returning to base state.
    • DEFAULT_FORCE
      • Default value of forceThresh
    • DEFAULT_SPEED
      • Default value of pushSpeed
    • WALL_THRESH
      • Force reading to indicate the arm has returned to the wall.
    • PUSH_TIME
      • How long to allow the motor to push after the force threshold was met by the door sensor

State Machine

The internal state machine of the device is specified by the states below. Note that not all commands issued by each state are described, only those directly relevant to operation. For example, there is a Boolean newState variable in the code that is used to ensure the debug window isn’t flooded with “Listening for IR” lines. This and other under-the-hood logic is excluded.

BOOT

Initialization stage where pushSpeed and forceThresh parameters are set to their default values of COAST_SPEED and FORCE_MAX.

Always transitions to LISTEN.

LISTEN

Stage where the device waits for IR input and acts accordingly. This is essentially the “idle” state.

  1. Check which button pressed
    1. No input
      1. Remain in LISTEN.
    2. Channel up/down
      1. Increment the pushSpeed by the SPEED_INC constant
      2. Output new speed to the LCD
      3. Remain in LISTEN
      4. Note: by design of the remote, these signals must be issued very deliberately. That is, the user must click, release for a moment, then click again to change the value twice.
    3. Volume up/down
      1. Increment the forceThresh by the FORCE_INC constant
      2. Output new door weight to LCD
      3. Remain in LISTEN
      4. Note: by design of the remote, these signals always come in groups of at least 3 and allow holding the button down.
    4. If Exit
      1. Transition to BOOT
      2. This is essentially a reset
    5. If OK
      1. Set held to true.
      2. Transition to CLOSE_DOOR
      3. This is the command to start closing the door

CLOSE_DOOR

First state of a chain of states that work to close the door.

  1. Output a message to the LCD
  2. Transition to CHECK_IR

CHECK_IR

Checks for a signal from the user. If ANY signal is detected, the motor will immediately stop.

  1. Check for IR Signal
    1. If no signal detected, set held to false and transition to CHECK_FORCE.
    2. If a signal was detected and held is false, transition to RESET.

CHECK_FORCE

Checks the value of the force sensor that is/will be in contact with the door and adjusts behavior accordingly.

  1. Check the force reading doorForce
    1. If doorForce is less then MIN_FORCE,
      1. speed = COAST_SPEED
      2. Transition to RUN_MOTOR
    2. If doorForce is less than forceThresh
      1. speed = pushSpeed
      2. Transition to RUN_MOTOR
    3. Otherwise
      1. Wait for PUSH_TIME seconds.
      2. Transition to RESET

RUN_MOTOR

Simply sets the speed of the motor

  1. Set motor speed to speed
  2. Transition to CHECK_IR

RESET

Begins the process of returning the device to base position.

  1. Set the speed to 0 (Motor cannot switch from forward to backward in one speed change)
  2. Set the speed to REVERSE_SPEED
  3. Transition to CHECK_WALL

CHECK_WALL

Determine if the arm has returned to its home position.

  1. Check wall force sensor and check for IR
    1. If wall force greater than WALL_THRESH or there was an IR signal detected
      1. Set speed to 0
      2. Transition to LISTEN
    2. Otherwise remain in CHECK_WALL

Problems Encountered

The primary problem encountered was that the codes transmitted to the program seemed to have overlap between different buttons. That is, the byte arrays for button X and button Y were identical across all 32 bytes. The next step for solving this would be to research what exactly could be causing this and how the industry handles this.

The Program

Import programDoor_Slamming_Device

Slams doors

Code

The main.cpp file that contains the main program loop with the state machine.

main.cpp

#include "mbed.h"
#include "func.h"
#include "define.h"
#include "globlvar.h"


int main()
{
    int myButton;
    //For developer use
    //GetIRCodes();
    //GetForces();
    format = RemoteIR::SONY;
    while(1) {
        switch( state) {
            case BOOT:
                pc.printf("BOOTING UP\n\r");
                forceThresh = DEFAULT_FORCE;
                pushSpeed = DEFAULT_SPEED;
                state = LISTEN;
                newState = true;
                held = false;
                lcd.cls();
                lcd.printf("RON SWANSON \nDOOR SLAMMER!");
                break;
            case LISTEN:
                //Listen for IR signal                
                if(newState)
                    pc.printf("LISTENING FOR IR\n\r");
                newState = false;
                myButton = IRListen();

                switch(myButton) {
                    case NONE:
                        break;
                    case RUN:
                        pc.printf("OK Button\n\r");
                        state = CLOSE_DOOR;
                        newState = true;
                        held = true;
                        break;
                    case SPD_UP:
                        if(pushSpeed+SPEED_INC<=1)
                            pushSpeed += SPEED_INC;
                        else
                            pushSpeed = 1;
                        speedPercent = (int)(pushSpeed*100.0f);
                        lcd.cls();
                        lcd.printf("Speed:\n%3d%%\n\r", speedPercent);
                        pc.printf("Speed:\n%3d%%\n\r", speedPercent);
                        break;
                    case SPD_DN:
                        if(pushSpeed-SPEED_INC>=SPEED_MIN)
                            pushSpeed -= SPEED_INC;
                        else
                            pushSpeed = SPEED_MIN;
                        speedPercent = (int)(pushSpeed*100.0f);
                        lcd.cls();
                        lcd.printf("Speed:\n%3d%%\n\r", speedPercent);
                        pc.printf("Speed:\n%3d%%\n\r", speedPercent);
                        break;
                    case RST:
                        pc.printf("Exit Button\n\r");
                        state = BOOT;
                        break;
                    case FRC_UP:

                        if(forceThresh+FORCE_INC<=1)
                            forceThresh+=FORCE_INC;
                        lcd.cls();
                        lcd.printf("Force threshold:\n%f pounds\n", (forceThresh*200.0f)/4.44822162f);
                        pc.printf("Force threshold:\n\r%f pounds\n\r", (forceThresh*200.0f)/4.44822162f);
                        break;
                    case FRC_DN:
                        if(forceThresh-FORCE_INC>0)
                            forceThresh-=FORCE_INC;
                        lcd.cls();
                        lcd.printf("Force threshold:\n%f pounds\n", (forceThresh*200.0f)/4.44822162f);
                        pc.printf("Force threshold:\n\r%f pounds\n\r", (forceThresh*200.0f)/4.44822162f);
                        break;
                    default:
                        pc.printf("Unknown Button: %d\n\r", myButton);
                        break;
                }
                break;
            case CLOSE_DOOR:
                lcd.cls();
                lcd.printf("GET OUT\n");
                pc.printf("CLOSING DOOR\n\r");
                state = CHECK_IR;
                break;
            case CHECK_IR:
                pc.printf("CHECKING FOR CANCEL SIGNAL\n\r");
                if(IRListen()==-1)
                    held = false;
                else if(!held) {
                    pc.printf("CANCEL SIGNAL RECEIVED\n\r");
                    state = RESET;
                    break;
                }
                state = CHECK_FORCE;
                break;
            case CHECK_FORCE:
                force = doorForce;
                pc.printf("CHECKING FORCE: %f\n\r", force);
                if(force<FORCE_MIN)
                    speed = COAST_SPEED;
                else if(force<forceThresh)
                    speed = pushSpeed;
                else {
                    wait(PUSH_TIME);
                    state = RESET;
                    break;
                }
                state = RUN_MOTOR;
                pc.printf("\tNEW SPEED: %f\n\r", speed);
                break;
            case RUN_MOTOR:
                myMotor.speed(DIRECTION*speed);

                state = CHECK_IR;
                break;
            case RESET:
                pc.printf("RETURNING TO IDLE\n\r");
                myMotor.speed(0);
                myMotor.speed(DIRECTION*REVERSE_SPEED);
                state = CHECK_WALL;
                break;
            case CHECK_WALL:
                wall = wallForce;
                if(wall>=WALL_THRESH||IRListen()>=0) {
                    myMotor.speed(0);
                    state = LISTEN;
                    lcd.printf("AND STAY OUT!");
                    pc.printf("WALL DETECTED, ENTERING IDLE STATE\n\r");

                    break;
                }
                pc.printf("CHECKING WALL: %f\n\r", wall);

                break;
            default:
                break;
        }


    }
}

The func.h file contains external functions used by the main program.

func.h

#include "globlvar.h"
#ifndef FUNC_H
#define FUNC_H

//Function that checks the IR Receiver for a message, returning the first byte of the array, 
//which is a unioque between the buttons used by this device.
int IRListen()
{
    uint8_t buf[32];
    int bitcount = 0;
    if (ir_rx.getState() == ReceiverIR::Received) {
        bitcount = ir_rx.getData(&format, buf, sizeof(buf) * 8);
    } else {
        return -1;
    }
    if(bitcount > 0) {
        //for(int i = 0; i < bitcount; i++) {
            pc.printf("%d  ", buf[0]);
        //}
        pc.printf("\n\r");
        return buf[0];
    }
    return -1;

}
//Debugging application used to get the IR codes for each button press.
void GetIRCodes()
{
    while(1) {
        uint8_t buf[32];
        int bitcount = 0;
        if (ir_rx.getState() == ReceiverIR::Received) {
            bitcount = ir_rx.getData(&format, buf, sizeof(buf) * 8);
            if(bitcount > 0) {
                pc.printf("Bitcount: %d, sizeof(buf): %d\n\r", bitcount, sizeof(buf)*8);
                for(int i = 0; i < 32; i++) {
                    pc.printf("%d  ", buf[i]);
                }
                pc.printf("\n\r");
            }
        }
        wait(.1);
    }
}
//Debugging function that outputs the forces on each sensor.
void GetForces()
{
    while(1)
    {
        force = doorForce;
        wall = wallForce;
        lcd.printf("Door: %f\nWall: %f", force, wall);
        wait(.3);
    }
}   

#endif

The globlvar.h file contains all global variables and enumeration values.

globlvar.h

#include "mbed.h"
#include "ReceiverIR.h"
#include "Motor.h"
#include "TextLCD.h"

#ifndef GLOBLVAR_H
#define GLOBLVAR_H

ReceiverIR ir_rx(p15);
Serial pc(USBTX, USBRX);
DigitalOut leds[] = {(LED1), (LED2),(LED3), (LED4)};
AnalogIn doorForce(p16);
AnalogIn wallForce(p20);
Motor myMotor(p23,p6,p5);
TextLCD lcd(p24, p25, p26, p27, p28, p29, TextLCD::LCD20x4); // rs, e, d4-d7

bool newState=false;
bool held;
bool signal;
float force;
float wall;
float speed;
int speedPercent;

RemoteIR::Format format;
float pushSpeed;
float forceThresh;

enum RunState
{
    BOOT = 0,
    LISTEN,
    CLOSE_DOOR,
    CHECK_IR,
    CHECK_FORCE,
    CHECK_WALL,
    RUN_MOTOR,
    RESET,
};


RunState state = BOOT;

#endif

The define.h file defines all preprocessor directives used by the program.

define.h

#include "globlvar.h"

//Put all variables that must be accessible to all functions here.

#ifndef DEFINE_H
#define DEFINE_H

#define PRINT(message)  pc.printf( (message) );

#define PRINTLN(message)    pc.printf( (message) );\
                            pc.printf("\n\r");
                            
#define LED(i)  leds[i]

#define LED_0 1
#define LED_1 2
#define LED_2 4
#define LED_3 8

#define CODE_ON 
#define FORWARD 1
#define BACKWARD -1


//OPTIONS

//Which side of the arm is the door
#define DIRECTION BACKWARD
//Minimum force to start pushing
#define FORCE_MIN .05
//Default value of the force threshold
#define DEFAULT_FORCE .4
//Default value for the speed
#define DEFAULT_SPEED 1
//Speed of the arm when not in contact with door
#define COAST_SPEED .3
//Speed of the arm when returning to wall
#define REVERSE_SPEED -0.3
//Force threshold for contact with wall
#define WALL_THRESH .2
//How long to push after contacting door
#define PUSH_TIME .5
//How much the speed changes with each button press
#define SPEED_INC 0.05f
//How much the force changes with each button press
#define FORCE_INC 0.1f



#define SPEED_MIN COAST_SPEED

//BUTTON DEFINITIONS
#define NONE -1
#define SPD_DN 2
#define SPD_UP 3
#define FRC_UP 0
#define FRC_DN 1
#define RUN 14
#define RST 69



#define SET_LEDS(mask, state)   LED(0) = ((mask)&1!=0); \
                                LED(1) = ((mask)&2!=0); \
                                LED(2) = ((mask)&4!=0); \
                                LED(0) = ((mask)&8!=0); 
#define ON  1
#define OFF 0


#endif


Please log in to post comments.