An Elegant & Reliable Door Sensor

Schematic for door sensor.

In this configuration, with the second iteration of my sensing algorithm, draws ~10ma idle – near the absolute minimum for an Arduino Leonardo. Diagram created with Fritzing.

Last Thursday, October 3rd of 2013 (when I started writing this, as I am so very productive), I was thinking about door sensing – how could I measure the state of the door? I brainstormed with my roommate (Ryan Kubik, mechanical engineering), and we came up with all kinds of ideas; magnetic (à la home security systems), mechanical, ultrasonic, and acceleration, among others.

At one point, I remembered Jack Andraka’s discovery of an electrical-resistance-of-blood test for pancreatic cancer, and started poking around with my multimeter. It’s truly amazing what happens when someone throws a multimeter at a problem.

I turned the multimeter to continuity test mode, and in no time I noticed that the strike plate (the metal thing in the wall, where the bolt docks) is electrically connected to the handle!

Fifteen minutes later, I’d wired an arduino & PowerSwitch Tail up to the door. I modified the sample “Blink” program to read a digital pin (in an ugly loop), turning the (built-in LED on pin 13) on if the door’s voltage is high. I connected the arduino’s ground to the strike plate, and the to-read pin to the handle. I attached 10k pullup resistors to the breadboard’s power rails, so that the closed door connected the pin to ground, and the open door allowed the pullup resistor to pull the pin to the +5v rail (split by the pull up/down resistors to 2.5v). Dangling wires blocked the door. It was ugly and a hack, but functional!

While tweaking, I mistakenly connected 5v to the strike plate. Much to my surprise, I saw the arduino power off (thanks polyfuse! – this device cuts power when it detects a short) only to turn on when disconnected from the strike plate! The strike plate had to be sinking enough current to act as a short – which could only happen if it was connected to something with a massive sinking capacity – ground! Any connection to the arduino’s ground is therefore redundant – thereby I require only ONE connection to the door. I quickly confirmed this. The device was starting to take shape.

I knew that design decisions at this stage greatly affect my future options – time to start thinking ahead. I envisioned it wirelessly transmitting the “door open”/”door closed” signal. I also knew that it’s tremendously inconvenient (and a trip hazard) to run extension cords across a room, to the door – It’d have to be battery-powered. Out again, comes the multimeter.

For two reasons, I decided to power the arduino with 8 AA batteries, connected via snaps to a male barrel jack adapter:

Closeup of the battery holder.

Closeup of the battery holder.

The snaps connected to the battery holder & the barrel jack connector

The snaps connected to the battery holder & the barrel jack connector

An 8 pack of AA batteries is quite a power supply for a microcontroller – it will definitely last longer than a puny 9V battery; I can’t produce a regulated 5v for the micro-usb connector, so I’ll have to put up with the inefficient (read: piece of crap) voltage regulator on the arduino. The built-in linear voltage regulator uses about 6ma with NO load, and downconverts via dissipation of energy as heat. When I would later measure the power draw of the arduino, I measure it on the 12V side of the circuit, as that is the actual power draw.

The first working iteration of my code was very power inefficient. I patched my multimeter into the circuit, and measured ~30ma while IDLE (door closed), and ~35ma while active (door open).


void loop(){
	buttonState = digitalRead(buttonPin);
	if (buttonState == HIGH) {
		relay(true);
		delay(500);
	}
	else {
		relay(false);
	}

The two biggest , and also the most convenient, improvements possible for this configuration are elimination of the infinite polling loop, and using a pin OTHER than 13.

As is, the microcontroller is checking the input pin as fast as it can. As a matter of fact, it’s full-throttle checking the input pin one hundred percent of the time. Any microcontroller that is actively executing must power the entire chip – clock distribution, leakage current, extraordinary transient loads presented by switching logic, among a great many – and that power is far more than tolerable. The loop() function is exactly that, and will continue to suck power indefinitely (or the battery dies )


void loop(){
  delay(500);
  powerOFF();
}

In fact, that loop could even consist entirely of NOPs (No OPeration; do nothing), and it would still suck power!

How else can we sense a change in the door, AND take action? The answer lies in the hardware interrupt. A hardware interrupt is a special feature in all modern µControllers/µProccessors that, upon activation, forces the processor to stop any ongoing execution (if any), and execute code specified as an Interrupt Service Routine by the program. The arduino IDE provides the attachInterrupt(pin_number,ISR,type_of_interrupt) function (it’s technically a macro; the compiler just replaces it with the AVR-specific syntax), which defines the pin on which the interrupt is assigned to, the function that will act as the ISR, and the TYPE of interrupt that will trigger the ISR. There are several types of interrupts – RISING, FALLING, LOW, & CHANGE – one of which must be assigned in the third argument to attachInterrupt. For more on the arduino interrupts, see:

The documentation, (mirror),

This forum post, (mirror),

and the two github source files linked in the aforementioned post, (Arduino_hardware_arduino_cores_arduino_Arduino (mirror)), (Arduino_hardware_arduino_cores_arduino_WInterrupts (mirror)).

The interrupt allows us to enable a special kind of “Sleep” mode which saves a tremendous amount of power.

avr powerdown

See the documentation for more
(http://www.atmel.com/Images/doc7766.pdf) ((mirror))

I’ve removed the debugging information for readability, the source code looks like this:


/*
 Button
 Turns on and off a light emitting diode(LED) connected to digital
 pin 13, when pressing a pushbutton attached to pin 2.
 The circuit:
 * LED attached from pin 13 to ground
 * pushbutton attached to pin 2 from +5V
 * 10K resistor attached to pin 2 from ground

 * Note: on most Arduinos there is already an LED on the board
 attached to pin 13.

 created 2005
 by DojoDave
 modified 30 Aug 2011
 by Tom Igoe

 DOOR:
 With 270 ohm resistor, uses ~30ma idle & relay on
 pin 11 instead of 13 saves 1ma
 uses 8ma total when idle WITH sleep
 ncp1117 uses 6ma typ (10ma max) Quiescent
 */

// constants won't change. They're used here to
// set pin numbers:

#include <avr/sleep.h>

const int buttonPin = 2; // the number of the pushbutton pin
const int ledPin = 11; // the number of the LED pin

// variables will change:
int buttonState = 0; // variable for reading the pushbutton status

void setup() {
 // initialize the LED pin as an output:
 pinMode(ledPin, OUTPUT);
 // initialize the pushbutton pin as an input:
 pinMode(buttonPin, INPUT);
 //digitalWrite(buttonPin,LOW);

 delay(10000);
 wake_ISR();
}

void wake_ISR(){
 buttonState = digitalRead(buttonPin);
 if (buttonState == HIGH) {relay(true);}
 else {relay(false);}
}

void powerOFF(){
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
attachInterrupt(buttonPin,wake_ISR,CHANGE);
sleep_mode();
sleep_disable();
detachInterrupt(0);
}

void loop(){

 delay(500);
 powerOFF();
}

void relay(boolean on){
 // the relay turns off when led is ON, and turns on when led is OFF
 if (!on){
 digitalWrite(ledPin, HIGH);
 }
 else{
 digitalWrite(ledPin, LOW);
 }

}

High-Level overview of the code:

First, I assign the pins as in the schematic at the top of the article:

void setup: door_setup

The delay is for debugging – so when I apply power to the µC, I can see that ledPin works.

Next, I define the ISR that we’ll see assigned in a moment:

door_ISR

I’ve abstracted from the actual state of the pin attached to the relay by defining a relay(boolean) function – it can be counterintuitive and thereby confusing when toggling.

Now, I define the all important sleep function:

door_poweroff

Then, (jumping out of the order in the source code, which doesn’t matter to the compiler)  I define the relay abstracting function:

door_relay

As if #DEFINE TRUE FALSE wasn’t enough

And lastly, a ridiculously short loop:

door_loop

The delay(500) is a really ugly bit of software debouncing

Program flow:

door_flow

Created with Wolfram Mathematica. Source code:

LayeredGraphPlot[{"on" -> "setup()","setup()" -> "pinMode(ledPin,OUTPUT)",{"setup()" -> "loop()", "exit setup()"},"pinMode(ledPin,OUTPUT)" -> "pinMode(buttonPin,INPUT)","pinMode(buttonPin,INPUT)" -> "wake_ISR()",{"wake_ISR()" -> "setup()", "return from setup()"},"wake_ISR()" -> "digitalRead(buttonPin)","digitalRead(buttonPin)" -> "relay(TRUE)","digitalRead(buttonPin)" -> "relay(FALSE)","relay(TRUE)" -> "digitalWrite(ledPin,LOW)","digitalWrite(ledPin,LOW)" -> "wake_ISR()","relay(FALSE)" -> "digitalWrite(ledPin,HIGH)","digitalWrite(ledPin,HIGH)" -> "wake_ISR()","powerOFF()" -> "loop()","loop()" -> "powerOFF()","powerOFF()" -> "set_sleep_mode(SLEEP_MODE_PWR_DOWN)","set_sleep_mode(SLEEP_MODE_PWR_DOWN)" -> "sleep_enable()","sleep_enable()" -> "attachInterrupt(buttonPin,wake_ISR,CHANGE)","attachInterrupt(buttonPin,wake_ISR,CHANGE)" -> "sleep_mode()","sleep_mode()" -> "sleep_disable()","sleep_disable()" -> "detachInterrupt(0)",{"detachInterrupt(0)" -> "powerOFF()", "exit"},"INTERRUPT" -> "wake_ISR","wake_ISR" -> "wake_ISR()",{"wake_ISR()" -> "loop()", "from ISR"}},VertexLabeling -> True, ImageSize -> Large]

Low-Level flow:

When the program actually executes, setup calls digitalWrite to set ledPin & buttonPin;  setup calls  wake_ISR(), determining the initial state of the door; wake_ISR() then calls relay(boolean) to set the state of the relay – and setup() returns.  Next, loop() calls powerOFF(); powerOFF contains a bunch of low-level macros, i.e

SLEEP_MODE_PWR_DOWN:

#if defined(__AVR_ATmega161__)
 #define SLEEP_MODE_IDLE 0
 #define SLEEP_MODE_PWR_DOWN 1
 #define SLEEP_MODE_PWR_SAVE 2
 #define set_sleep_mode(mode) \
 do { \
 MCUCR = ((MCUCR & ~_BV(SM1)) | ((mode) == SLEEP_MODE_PWR_DOWN || (mode) == SLEEP_MODE_PWR_SAVE ? _BV(SM1) : 0)); \
 EMCUCR = ((EMCUCR & ~_BV(SM0)) | ((mode) == SLEEP_MODE_PWR_SAVE ? _BV(SM0) : 0)); \
 } while(0)

sleep_enable():

#define sleep_enable() \
do { \
 _SLEEP_CONTROL_REG |= (uint8_t)_SLEEP_ENABLE_MASK; \
} while(0)

also: sleep_mode(), & sleep_disable().

If you want to see for yourself: the source is here, and mirrored here.

powerOFF prepares the arduino for sleep(set_sleep_mode, sleep_enable), attaches an interrupt to buttonPin (the one connected to the door), before finally enabling sleep mode (sleep_mode). When the interrupt is triggered, program execution resumes at sleep_disable() (to stop sleeping), and immediately thereafter the Interrupt Service Routine begins. wake_ISR simply turns the relay on if the door’s voltage is enough to register as a digital HIGH, or off if digital LOW. Then execution jumps to loop(), which calls powerOFF and immediately puts the arduino back to sleep.

Looking forward

My next move will be to further reduce power, and later add radio-frequency communication. I have some SparkFun cheap-o R/F breakouts, and all I need is time.

There are some great power saving techniques here: http://gammon.com.au/power (mirrored: Electronics _ Microprocessors _ Power saving techniques for microprocessors)

Postscript

I’ve been doing some very cool things here at Poly, but I’m very busy. I don’t always get a chance to keep up with everybody(i.e. I can barely keep up with anybody) – Shoutout to Mr.Zachry!

~ by Alexander Riccio on October 20, 2013.

8 Responses to “An Elegant & Reliable Door Sensor”

  1. […] provided they’re well documented. We hope that the Hackaday readers also think that the door sensor that [Alexander] developed falls into this category. Instead of using common methods such as a magnet + reed switch, he […]

    Like

  2. […] provided they’re well documented. We hope that the Hackaday readers also think that the door sensor that [Alexander] developed falls into this category. Instead of using common methods such as a magnet + reed switch, he […]

    Like

  3. Very well documented.The Interupts are hard for me to follow but this runs on my arduino and power saving is a huge problem for me . Thanks

    Like

  4. I should mention for old 70 year eyes the black background makes reading difficult . If anyone else has problems turn the stle off by View->Use Style->None gives a white background .
    Control Plus a few times and Bingo !

    Like

    • Interesting…I could never have predicted that the black background could be a problem! (oh the joy of hindsight)

      ctrl + a (or whatever the ‘select all’ shortcut is on your platform) should also help.

      Like

  5. I posted this on hackaday sorry. Its probably not going to get much exposure now as the public moves on so I’ll duplicate it here . Hopefully you will aprove . Thanks for the tips above too they are good . I want to get interupts working for a radio project to cut battery power use

    “I’m trying it on a breadboard as per your fritzing and using the sense wire to contact positive and negative to operate it -no pushbutton. The problem seems to be not the wiring but the interupt code . It gets the first state when it resets but does not change after that no matter where I put the sense wire . I have Duemilanove boards and I have not noticed a polyfuse .Does your interupt code work on a 328P chip? Have you tried it at all . Are there perhaps some timer or register changes required for the 328P”

    I can add here that I have added debug Serial.println()’s all over the place to find why it does not run and can see that the interupt routine is not recognising the interupt at this stage

    Thanks again

    Like

    • I responded on Hackaday:
      “Duemilanove boards absolutely SHOULD have a polyfuse, so that shorting 5v ➡ ground cuts power to the board. Does shorting 5v ➡ the strike plate on your door have the same effect?

      Both the Leonardo and the Duemilanove have pin 2 mapped to an interrupt-supporting µc pin, and unless you’re writing assembly instead of C, you shouldn’t need to make any changes.

      what exactly is your sense wire connected to? What happens when you replace the resistors with jumpers?”

      Also, try setting up a timer interrupt (In ADDITION to, OR INSTEAD of), see if the arduino is woken up by that.

      Best of luck!

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
0xicf

I.C.F::Israel Cyber Forces

Modern

Modern C++ for the Windows Runtime

Krypt3ia

(Greek: κρυπτεία / krupteía, from κρυπτός / kruptós, “hidden, secret things”)

Andrzej's C++ blog

Guidelines and thoughts about C++

Bromium Labs

Call of the Wild Blog

fuzzing.info

the art of unexpected input engineering

Video Encoding & Streaming Technologies

Fabio Sonnati on video delivery and encoding

Freedom Embedded

Balau's technical blog on open hardware, free software and security

Paolo Bernardi

Paolo Bernardi, ramblings and notes (and Crypto-Gram for ebook readers)

The Embedded Code

Designing From Scratch

Bughira's Weblog

There is no such thing as closed source software...the processor sees every instruction, and so does the reverse engineer...

mov ah, 9<br>mov dx, hello_world_msg<br>int 21h

Just another WordPress.com weblog

Running the Gauntlet

Tank and Siko's Security Blog

mbrownnyc

so watch me do the funky dead butterfly.

The ThreatSTOP Blog

Stop Botnets Stealing from you

clevomods

home of the Custom light controller and LightFX library

Naked Security

Computer Security News, Advice and Research

root labs rdist

Embedded security, crypto, software protection

Biosingularity

Advances in biological systems.

Strategic Cyber LLC

A blog about Armitage, Cobalt Strike, and Red Teaming

Assumption Parish Police Jury

http://assumptionla.com/

Alexander Riccio

"Change the world or go home" -Microsoft Employee Slogan

Liquid Metals Project

Stuff that never made it into the paper.

We Are Made In NY

Learn, Launch and Find a Job in NYC Tech

Home Awesomation

It all started when I wanted to turn my fireplace on from my TV remote...

Mind Hacks

Neuroscience and psychology news and views.

tronixstuff

fun and learning with electronics

Cedar's Digest

Cognitive science, perception, teaching and ed reform

Walking Randomly

"Change the world or go home" -Microsoft Employee Slogan

Visual6502.org

"Change the world or go home" -Microsoft Employee Slogan

"Change the world or go home" -Microsoft Employee Slogan

Ken Shirriff's blog

"Change the world or go home" -Microsoft Employee Slogan

YouTube Blog

"Change the world or go home" -Microsoft Employee Slogan

Google Testing Blog

"Change the world or go home" -Microsoft Employee Slogan

Google Student Blog

"Change the world or go home" -Microsoft Employee Slogan

Google Research Blog

"Change the world or go home" -Microsoft Employee Slogan

Politics & Elections Blog

"Change the world or go home" -Microsoft Employee Slogan

Google Public Policy Blog

"Change the world or go home" -Microsoft Employee Slogan

Google Open Source Blog

"Change the world or go home" -Microsoft Employee Slogan

Google Online Security Blog

"Change the world or go home" -Microsoft Employee Slogan

Official Google for Work Blog

"Change the world or go home" -Microsoft Employee Slogan

Gmail Blog

"Change the world or go home" -Microsoft Employee Slogan

Inside Search

"Change the world or go home" -Microsoft Employee Slogan

"Change the world or go home" -Microsoft Employee Slogan

Follow

Get every new post delivered to your Inbox.

Join 966 other followers

%d bloggers like this: