Figure 1 |
Figure 2 |
Figure 3 |
Figure 4 |
This cat -- who I will henceforth refer to as "Ollie" (being his actual name) -- cherishes his time outdoors and spends much of his waking time roaming the neighbourhood, determining which variety of vole he'll attempt to leave on my bed that night. Unfortunately there is only one way for him to exit and enter my apartment during the day: an open window. Even more unfortunate is that the only window is positioned so that its top-most edge abuts the ceiling, effectively nullifying the meager offering of heat that my landlords are so generous enough to provide me. I had a plan though, and it wasn't to sit around complaining.
I knew the basic idea of Arduino and have a decent enough programming knowledge, so I decided to give it a shot. My wallet took a sizable hit in order to buy the microcontroller board, soldering station, multimeter, helping hands, solder, cordless drill, hand saw...*INHAAAALE*... wires, measuring tape, electrical tape, two sided tape, tape tape, breadboards, resistors, beer and particle board, but I made the executive decision that all of this would be needed anyways once I became a full-fledged engineer and additionally crossed that ambiguous line into manhood.
With all my tools purchased, I was ready and set off to have a nervous breakdown -- I mean, build an automatic window opener. The first step was to relearn the trivial parts of programming that had atrophied in my brain long ago. After a few days of getting up to speed with the great knowledge emporium that Arduino has set up and after a few more of making several programs (detecting a switch and turning on an LED, detecting the presence or absence of light with a photoresistor), I drew up the outline of how I wanted to complete the project.
The outline of how the product would work goes as follows:
- Ollie steps on a pad
- Pad depresses one of 5 switches directly beneath it on a breadboard
- Signal is passed through the switch and back to Arduino
- Arduino -- via motor shield add-on -- turns on DC motor(s) which fling the window open
- Ollie waits for 5 minutes, half his body inside, half outside in order to maximize the amount of hot air being let out
- Ollie finally goes outside, letting his weight off the pad in the process
- Arduino waits for 10 seconds just to be sure not to inadvertently create a kitty guillotine
- Arduino reverses the direction of the motors and sends full power through them once more
- Window slams shut
- Al has peace of mind at work
Here's why this was a terrible idea (again, in point form):
- DC motors are meant for high speeds and extremely low torque, making them as suitable for this job as a clown is for real estate
- Ollie is apparently deathly afraid of clicking noises and/or strange pads
So it was back to the drawing board. I immediately recognized (read: figured out after a month) that DC motors were not the way to go. I needed the opposite end of the spectrum: slow and steady with high torque. That solved problem 1. Problem 2 was a bit different; I needed a solution that Ollie could not see or feel had altered his environment, being the prissy little lad he is. Two options came to mind in a proximity sensor or an RFID setup, and I went with the former but may eventually update the system for the latter (I'll talk about why later).
Problems determined, solutions found, I proceeded on with my new plan, which went like so:
- Ollie jumps up to window
- Sensor picks up object
- Sensor is continually feeding raw data back to Arduino
- Arduino interprets data as either above or below threshold determined in previous testing
- If above, cat wants out/in, so Arduino tells the servo to change its angular position to "open"
- If below, nothing there, so Arduino keeps window closed
(of note here is that there is extra code to determine what to do when the window is open and an object is sensed or if it is closed with no object sensed)
- Ollie heads in or out unmolested
Now that I had a plan and the equipment, I needed to fabricate a mount for the servo as well as an arm to screw into the servo (Figure 5), such that this arm could pull or push the window.
Figure 5 |
It worked like a charm. Then the engineering gods once again decided that things were running far too smoothly at this point, and so they decided to obliterate the 5 V regulator on the board (Figure 6).
Figure 6 |
Figure 7 |
Finally... finally, I had everything that I needed. I had my servo setup, I had my proximity sensor, I had my sticky tack and push pins for securing, and I had my code (which I will append to the end of this post).
After much fine tuning and more tack and pins than a kindergarten classroom, I had completed the set up and was ready for a live demonstration with a member of the audience.
A success for the ages.
"Why would you need an RFID setup, Al?" you might ask, adding "The proximity sensor seems to work fine."
Right you are. The problem now is that it works TOO well, as in any animal that would like to take a peek in my room can help themselves. Story time:
I was fast asleep at around 3 in the morning on a blustery February evening when I was awoken by the window opening and slamming shut, opening and slamming shut (this was before I updated the program to monitor the current status of proximities and instead just close 10 seconds after opening). So I clicked on my bedside lamp, thinking that I would see young Oliver in a confused state at the window. I was wrong, he was resting on my bed.
My eyes wandered up to the opening near my ceiling and happened upon our friend, the raccoon, just as the window slid into his head with another "THUNK."
"......I need to make an update" was all I thought as I shooed him away with a broomstick.
As promised, here is the code:
/* Sketch that reads a proximity value from a sensor and
opens or closes a servo attached to a window based on the reading.
The VCNL4000 communication section is taken from Adafruit's
public-use code found here https://github.com/adafruit/VCNL4000
*/
// Include the requisite libraries to
// talk to the servo and talk to the sensor,
// respectively
#include <Servo.h>
#include <Wire.h>
Servo myservo; // the servo object to be used
/* The motor control board offered by Arduino
has a section for motors that uses several parameters
(PWM, direction, brake) for two, unique motors.
The board appears to have a junction for servos, the
proble -- at least for my project -- is that the pin
locations for the junction do not align with the
pin locations on the servo's input. So I've used the
motor connections to supply full, continuous 5 volts
and changed the position of the servo with functions
from the servo library
*/
const int dirPin = 12;
const int pwm = 3;
const int brake = 9;
int velocity = 0; // make sure the servo won't move when program starts
int pos = 0; // give the position variable a default value
unsigned long startTime = 0; // initialize time variables
unsigned long currentTime = 0;
// Adafruit coding section
// the i2c address
#define VCNL4000_ADDRESS 0x13
// commands and constants
#define VCNL4000_COMMAND 0x80
#define VCNL4000_PRODUCTID 0x81
#define VCNL4000_IRLED 0x83
#define VCNL4000_AMBIENTPARAMETER 0x84
#define VCNL4000_AMBIENTDATA 0x85
#define VCNL4000_PROXIMITYDATA 0x87
#define VCNL4000_SIGNALFREQ 0x89
#define VCNL4000_PROXINITYADJUST 0x8A
#define VCNL4000_3M125 0
#define VCNL4000_1M5625 1
#define VCNL4000_781K25 2
#define VCNL4000_390K625 3
#define VCNL4000_MEASUREAMBIENT 0x10
#define VCNL4000_MEASUREPROXIMITY 0x08
#define VCNL4000_AMBIENTREADY 0x40
#define VCNL4000_PROXIMITYREADY 0x20
// end of Adafruit coding
void setup() {
Serial.begin(9600); // Begin sending information to the screen
Serial.println("VCNL");
Wire.begin();
// digital pin two on the Arduino board will be used
// for sending information to the servo
myservo.attach(2);
// initialize the motor pins as outputs
pinMode(dirPin, OUTPUT);
pinMode(pwm, OUTPUT);
pinMode(brake, OUTPUT);
// Adafruit coding section
// check to see if the board has recognized that
// the proximity sensor is connected/capable of
//communicating
uint8_t rev = read8(VCNL4000_PRODUCTID);
if ((rev & 0xF0) != 0x10) {
Serial.println("Sensor not found :(");
while (1);
}
// The following section is meant to provide the programmer
// with some sort of idea of the signal being sent to and received
// from the sensor
write8(VCNL4000_IRLED, 20); // sending 20 * 10mA = 200mA of current to IR light
Serial.print("IR LED current = ");
Serial.print(read8(VCNL4000_IRLED) * 10, DEC);
Serial.println(" mA");
//write8(VCNL4000_SIGNALFREQ, 3);
Serial.print("Proximity measurement frequency = ");
uint8_t freq = read8(VCNL4000_SIGNALFREQ);
if (freq == VCNL4000_3M125) Serial.println("3.125 MHz");
if (freq == VCNL4000_1M5625) Serial.println("1.5625 MHz");
if (freq == VCNL4000_781K25) Serial.println("781.25 KHz");
if (freq == VCNL4000_390K625) Serial.println("390.625 KHz");
write8(VCNL4000_PROXINITYADJUST, 0x81);
Serial.print("Proximity adjustment register = ");
Serial.println(read8(VCNL4000_PROXINITYADJUST), HEX);
// arrange for continuous conversion
//write8(VCNL4000_AMBIENTPARAMETER, 0x89);
// end of Adafruit section
}
// Adafruit function to let the sensor know we'll be retrieving
// a proximity value from it and then returning that value
uint16_t readProximity() {
write8(VCNL4000_COMMAND, VCNL4000_MEASUREPROXIMITY);
while (1) {
uint8_t result = read8(VCNL4000_COMMAND);
//Serial.print("Ready = 0x"); Serial.println(result, HEX);
if (result & VCNL4000_PROXIMITYREADY) {
return read16(VCNL4000_PROXIMITYDATA); // the value we're after
}
delay(1);
}
}
void loop(){
// Supply +5 V to the servo
digitalWrite(dirPin,HIGH);
digitalWrite(brake,LOW);
velocity = 255;
analogWrite(pwm,velocity);
// close the window just in case it was open prior
// to running this program
for(pos = 105; pos < 145; pos += 1)
{
myservo.write(pos);
delay(15);
}
// Adafruit code section
// read ambient light. This isn't entirely necessary for this project
// but it could become relevant during later design iterations
write8(VCNL4000_COMMAND, VCNL4000_MEASUREAMBIENT | VCNL4000_MEASUREPROXIMITY);
while (1) {
uint8_t result = read8(VCNL4000_COMMAND);
//Serial.print("Ready = 0x"); Serial.println(result, HEX);
if ((result & VCNL4000_AMBIENTREADY)&&(result & VCNL4000_PROXIMITYREADY)) {
// supply the servo with 0 V to prevent needless movement
velocity = 0;
analogWrite(pwm,velocity);
// output the proximity and ambient light data to the screen
Serial.print("Ambient = ");
Serial.print(read16(VCNL4000_AMBIENTDATA));
Serial.print("\t\tProximity = ");
Serial.println(read16(VCNL4000_PROXIMITYDATA));
// End of Adafruit code
// The proximity sensor has an accurate detection range of about 3 to 4 inches
// when something gets inside this distance and occupies a large enough angular
// size, the proximity value returned from the sensor will be above 2600
if (read16(VCNL4000_PROXIMITYDATA) > 2600 && (myservo.read() >= 105)) {
// this section of code only runs when something is in range
// and the window is closed or the closing sequence has been
// initiated
// Supply +5 V to the servo
digitalWrite(dirPin,HIGH);
digitalWrite(brake,LOW);
velocity = 255;
analogWrite(pwm,velocity);
// move the servo to 80 degrees in one movement to give
// the servo arm inertia and overcome static friction
// and then decrease the degrees in one degree incriments
// until the servo arm is at 40 degrees
for(pos = 80; pos > 40; pos -= 1)
{
myservo.write(pos);
delay(15);
}
delay(100);
// supply the servo with 0 V to prevent needless movement
velocity = 0;
analogWrite(pwm,velocity);
}
else if(read16(VCNL4000_PROXIMITYDATA) <= 2600 && (myservo.read() < 105)) {
// here we have the window closing code. It is stepped into when
// the sensor is not reading any nearby objects and the window is open
// supply the servo with 5 V to close
velocity = 255;
analogWrite(pwm,velocity);
// start the count to 10 seconds of no object sensed
startTime = millis();
currentTime = millis();
// continue looping until either an object is sensed or
// ten seconds have passed without sensing an object
while (currentTime - startTime < 10000){
if (read16(VCNL4000_PROXIMITYDATA) > 2600){
break;
}
currentTime = millis();
}
// breaking from the previous loop means that
// the ten second mark was not reached
// because an object was sensed. If this is the case
// we want to go to the beginning and sense again
if (currentTime - startTime < 10000){
continue;
}
// At this point, the requisite ten seconds have passed and
// we need to close the window
// just like opening, we want to overcome static friction
// with one, big position change (40 deg --> 105 deg)
// and then ease into the closed position
for(pos = 105; pos < 145; pos += 1)
{
// at any point in the closing process, an object might come
// into proximity, and since we don't want to squish it
// we have to hault the closing process and start over
if (read16(VCNL4000_PROXIMITYDATA) > 2600){
break;
}
// no object? okay, move to the next position
myservo.write(pos);
delay(15);
}
// stop the servo from being able to move
velocity = 0;
analogWrite(pwm,velocity);
}
}
}
}
// Read 1 byte from the VCNL4000 at 'address'
uint8_t read8(uint8_t address)
{
uint8_t data;
Wire.beginTransmission(VCNL4000_ADDRESS);
#if ARDUINO >= 100
Wire.write(address);
#else
Wire.send(address);
#endif
Wire.endTransmission();
delayMicroseconds(170); // delay required
Wire.requestFrom(VCNL4000_ADDRESS, 1);
while(!Wire.available());
#if ARDUINO >= 100
return Wire.read();
#else
return Wire.receive();
#endif
}
// Read 2 byte from the VCNL4000 at 'address'
uint16_t read16(uint8_t address)
{
uint16_t data;
Wire.beginTransmission(VCNL4000_ADDRESS);
#if ARDUINO >= 100
Wire.write(address);
#else
Wire.send(address);
#endif
Wire.endTransmission();
Wire.requestFrom(VCNL4000_ADDRESS, 2);
while(!Wire.available());
#if ARDUINO >= 100
data = Wire.read();
data <<= 8;
while(!Wire.available());
data |= Wire.read();
#else
data = Wire.receive();
data <<= 8;
while(!Wire.available());
data |= Wire.receive();
#endif
return data;
}
// write 1 byte
void write8(uint8_t address, uint8_t data)
{
Wire.beginTransmission(VCNL4000_ADDRESS);
#if ARDUINO >= 100
Wire.write(address);
Wire.write(data);
#else
Wire.send(address);
Wire.send(data);
#endif
Wire.endTransmission();
}
No comments:
Post a Comment