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.
LCD connections
Pin number | TextLCD pins | mbed pins |
---|---|---|
1 | GND | 0V |
2 | VCC | 9V, via 10k resistor |
3 | VO | 0V, via 1k resistor |
4 | RS | p23 |
5 | RW | 0V |
6 | E | p24 |
7 | D0 | not connected |
8 | D1 | not connected |
9 | D2 | not connected |
10 | D3 | not connected |
11 | D4 | p25 |
12 | D5 | p26 |
13 | D6 | p27 |
14 | D7 | p28 |
IR Sensor
MBED | IR |
---|---|
Vout | VCC |
p15 | OUT |
GND | GND |
Force Sensors
Wall sensor
MBED | Force Sensor |
---|---|
Vout | Power |
p16 | Ain |
GND | GND |
Door Sensor
MBED | Force Sensor |
---|---|
Vout | Power |
p20 | Ain |
GND | GND |
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.
Connections with H-Bridge
MBED | HBridge |
---|---|
Vu | +5V |
GND | GND |
rev | IN_B |
fwd | IN_A |
pull-up | EN_A |
pull-up | EN_B |
pwm | PWM |
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.
- float pushSpeed
- 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
- SPEED_INC
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.
- Check which button pressed
- No input
- Remain in LISTEN.
- Channel up/down
- Increment the pushSpeed by the SPEED_INC constant
- Output new speed to the LCD
- Remain in LISTEN
- 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.
- Volume up/down
- Increment the forceThresh by the FORCE_INC constant
- Output new door weight to LCD
- Remain in LISTEN
- Note: by design of the remote, these signals always come in groups of at least 3 and allow holding the button down.
- If Exit
- Transition to BOOT
- This is essentially a reset
- If OK
- Set held to true.
- Transition to CLOSE_DOOR
- This is the command to start closing the door
- No input
CLOSE_DOOR
First state of a chain of states that work to close the door.
- Output a message to the LCD
- Transition to CHECK_IR
CHECK_IR
Checks for a signal from the user. If ANY signal is detected, the motor will immediately stop.
- Check for IR Signal
- If no signal detected, set held to false and transition to CHECK_FORCE.
- 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.
- Check the force reading doorForce
- If doorForce is less then MIN_FORCE,
- speed = COAST_SPEED
- Transition to RUN_MOTOR
- If doorForce is less than forceThresh
- speed = pushSpeed
- Transition to RUN_MOTOR
- Otherwise
- Wait for PUSH_TIME seconds.
- Transition to RESET
- If doorForce is less then MIN_FORCE,
RUN_MOTOR
Simply sets the speed of the motor
- Set motor speed to speed
- Transition to CHECK_IR
RESET
Begins the process of returning the device to base position.
- Set the speed to 0 (Motor cannot switch from forward to backward in one speed change)
- Set the speed to REVERSE_SPEED
- Transition to CHECK_WALL
CHECK_WALL
Determine if the arm has returned to its home position.
- Check wall force sensor and check for IR
- If wall force greater than WALL_THRESH or there was an IR signal detected
- Set speed to 0
- Transition to LISTEN
- Otherwise remain in CHECK_WALL
- If wall force greater than WALL_THRESH or there was an IR signal detected
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.