Arduino Snippets: Bare-naked Passive IR Sensor

The heap disgorged some bare passive IR / pyroelectric elements that, IIRC, came from Electronic Goldmine, described as SDA02-54 dual-element sensors. A bit of rummaging and a glance at Nicera’s Fine Datasheet says that can’t possibly be true: the SDA02-54 has a square window. The nearby SSAC10-11, however, has a round window and looks like a better match. Incidentally, that means the Fresnel IR lenses on the Electronic Goldmine site probably won’t work as intended, because the lenses typically produce multiple beams intended to focus on dual (or quad) elements. I suppose you could convert one Fresnel pattern into an IR telescope…

For my present purpose, however, a bare single-element pyroelectric detector will work just fine: the general idea is to detect things out there in front, not make decisions about what’s going on.

Under normal circumstances, where you want decisions, you’d use a module (from, say, Sparkfun) with a passive IR sensor in front of some circuitry that conditions the output and produces yes-no detections. LadyAda has a good description of the workings thereof & interfacings thereto, including a link to the BISS0001 analog chip that does most of the heavy lifting in low-end PIR modules.

What’s the fun in that?

A pyroelectric detector is basically a high-impedance element buffered by a JFET, with its drain and source terminals brought out. IR radiation produces a bias change on the gate, which connects to the (grounded) case through a very very very large-value resistor. That means we can build what amounts to a source follower around the JFET (with all the PIR stuff to the left of the gate not shown):

Passive IR Sensor

Passive IR Sensor

The output runs around half a volt, which is a bit low. If you were serious, you’d pass it through an op-amp to boost it by a factor of four or five to around 2.5 V, which would have the additional benefit of lowering the impedance to work better with the Arduino’s ADC input circuitry. For now, I’ll pipe the voltage directly to an Arduino analog input:

SSAC10-11 PIR Sensor - breadboard

SSAC10-11 PIR Sensor – breadboard

The linear Hall effect magnetic sensor and LM335 temperature sensor live just this side of the PIR can, sharing their VCC and ground connections in a most intimate manner. Remember, this is a breadboard, not a finished circuit… [grin]

The SSAC10-11 (if, indeed, that’s what it is) reports the voltage difference between a reference element shielded within the can and an active element exposed to incoming IR. The DC bias for that lashup produces 650 mV on the 47 kΩ source resistor (about 14 μA) and the internal arrangement produces a lower voltage (and thus current) when the exposed element sees a warmer object, which isn’t quite what I expected. Warming the can by direct finger contact produces an increasing voltage, due to heating the reference element and leaving the sensing element (relatively) cool, at least until conduction equalizes the elements.

I threw in a bit of averaging for each reading, not that it really matters:

#define PAVG 3

word ReadPIR(byte Pin) {
word Sense;

	Sense = analogRead(Pin);
	for (byte i = 1; i < PAVG; i++)
		Sense += analogRead(Pin);

	return Sense / PAVG;
}

The LED bargraph shows the current input as a single bar scaled between the minimum and maximum values, so that the display automatically adjusts to changing conditions. The boolean shift direction sends the bar upward on the breadboard LEDs as the PIR element sees warmer objects, which makes much more sense than showing the actual decreasing sensor voltage. The input generally rests in the green zone and both extremes show nice red bars:

	PIRSense = ReadPIR(PIN_PIR);
	PIRDelta = PIRSense - PIRMin;

	PIRMin = min(PIRMin,PIRSense);
	PIRMax = max(PIRMax,PIRSense);
	PIRRange = PIRMax - PIRMin;

	PIRShift = (9 * PIRDelta)/PIRRange;
	LEDBits = 0x00001 << PIRShift;
	SetBarBits(LEDBits);

In real life, you’d want a reset button, or some code that gradually drifts the extrema toward the running average of the input, so they’re not stuck forever.

Dumping the raw ADC reading on the LED character display is easy:

	sprintf(LEDCharBuffer,"%4d",PIRSense);
	WriteLEDString(LEDCharBuffer);

Updating the displays every 100 ms seems about right. It’s crazy sensitive to anything within its field of view; sitting down two feet away is good for a few counts and a palm at 30 cm gives you 15 counts. As expected, the increases and decreases fade away exponentially over the course of a few tens of seconds.

If you wanted to do it right, you’d put a shutter or rotating aperture wheel in front, then track the AC signal difference between “scene” and “reference” views. A tiny Peltier module to stabilize the can temperature would make a lot of sense, too. Or, hey, that LM335 could report the actual can temperature, perhaps with everything embedded in a big thermal mass inside an insulating jacket with a peephole to the outside world. All that’s in the nature of fine tuning…

The Arduino source code:

// Nicera SSAC10-11 Single PIR Sensor
// Ed Nisley - KE4ANU - November 2012

//#include <stdio.h>
//#include <math.h>

//----------
// Pin assignments

const byte PIN_PIR = A2;			// Passive IR sensor

const byte PIN_MOSI = 8;			// data to shift reg
const byte PIN_SCK  = 6;			// shift clock to shift reg
const byte PIN_RCKB  = 7;			// latch clock for LED Bargraph
const byte PIN_RCKC  = 12;			// latch clock for LED character display

const byte PIN_HEARTBEAT = 13;				// DO - Arduino LED

//----------
// Constants

const int UPDATEMS = 100;					// update LEDs only this many ms apart

#define TCCRxB 0x02							// Timer prescaler

#define LED_SIZE		4				// chars per LED
#define LED_DISPLAYS	1				// number of displays
#define LED_CHARS		(LED_DISPLAYS * LED_SIZE)

union DL1414_ {
	word ShiftWord;				// word overlay
	struct {					// bitfield sent to the display
	unsigned int Addr:2;
	unsigned int NotWrite:1;
	unsigned int Ctl3_7:5;			// unused bits
	unsigned int Data:7;
	unsigned int Data7:1;			// unused bit
	} ShiftBits;
};

//----------
// Globals

int PIRBase, PIRSense, PIRMin, PIRMax, PIRRange, PIRDelta;
int PIRShift;

word LEDBits = 0x5555;

char LEDCharBuffer[LED_CHARS + 1] = "HELO";		// raw char buffer, can be used as a string

unsigned long MillisNow;
unsigned long MillisThen;

//-- Helper routine for printf()

int s_putc(char c, FILE *t) {
  Serial.write(c);
}

//-- Send bits to LED bar driver register

void SetBarBits(word Pattern) {

	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,Pattern >> 8);
	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,Pattern & 0x00ff);

	digitalWrite(PIN_RCKB,HIGH);
	digitalWrite(PIN_RCKB,LOW);

}

void PulsePinHigh(byte PinID) {
	digitalWrite(PinID,HIGH);
	digitalWrite(PinID,LOW);
}

//-- Write single char to DL1414

void WriteLEDChar(char Char,char CharID) {

	union DL1414_ DL1414;

	DL1414.ShiftBits.Data = Char & 0x7F;
	DL1414.ShiftBits.Addr = ~CharID & 0x03;		// reverse order of chars

	DL1414.ShiftBits.NotWrite = 1;				// set up data and address

	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord >> 8);
	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord & 0x00ff);
	PulsePinHigh(PIN_RCKC);

	//	delay(1000);

	DL1414.ShiftBits.NotWrite = 0;				// write the character

	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord >> 8);
	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord & 0x00ff);
	digitalWrite(PIN_RCKC,HIGH);
	PulsePinHigh(PIN_RCKC);

	//	delay(1000);

	DL1414.ShiftBits.NotWrite = 1;				// disable write

	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord >> 8);
	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord & 0x00ff);
	PulsePinHigh(PIN_RCKC);

	//	delay(1000);

}

void WriteLEDString(char *pString) {

	for (byte i=0; (i < LED_CHARS) && *pString; ++i)
		WriteLEDChar(*pString++,i);

	return;
}

//-- Sample PIR with a dab of averaging

#define PAVG 3

word ReadPIR(byte Pin) {
word Sense;

	Sense = analogRead(Pin);
	for (byte i = 1; i < PAVG; i++)
		Sense += analogRead(Pin);

	return Sense / PAVG;
}

//------------------
// Set things up

void setup() {
	pinMode(PIN_HEARTBEAT,OUTPUT);
	digitalWrite(PIN_HEARTBEAT,LOW);	// show we arrived

//  TCCR1B = TCCRxB;					// set frequency for PWM 9 & 10
//  TCCR2B = TCCRxB;					// set frequency for PWM 3 & 11

	pinMode(PIN_MOSI,OUTPUT);
	digitalWrite(PIN_MOSI,LOW);

	pinMode(PIN_SCK,OUTPUT);
	digitalWrite(PIN_SCK,LOW);

	pinMode(PIN_RCKB,OUTPUT);
	digitalWrite(PIN_RCKB,LOW);

	pinMode(PIN_RCKC,OUTPUT);
	digitalWrite(PIN_RCKB,LOW);

	Serial.begin(9600);
	fdevopen(&s_putc,0);				// set up serial output for printf()

	printf("Passive IR sensor - SSAC10-11\r\nEd Nisley - KE4ZNU - November 2012\r\n");

	WriteLEDString(LEDCharBuffer);
	SetBarBits(LEDBits);

	PIRBase = ReadPIR(PIN_PIR);
	PIRMin = PIRBase - 5;
	PIRMax = PIRBase + 5;
	PIRRange = PIRMax - PIRMin;
	printf("Passive IR base: %d\n",PIRBase);

	delay(1000);

	MillisThen = millis();

}

//------------------
// Run the test loop

void loop() {

	MillisNow = millis();

	if ((MillisNow - MillisThen) > UPDATEMS) {
		digitalWrite(PIN_HEARTBEAT,HIGH);

		PIRSense = ReadPIR(PIN_PIR);
		PIRDelta = PIRSense - PIRMin;

		PIRMin = min(PIRMin,PIRSense);
		PIRMax = max(PIRMax,PIRSense);
		PIRRange = PIRMax - PIRMin;

//		printf("PIR: %d Min: %d Max: %d Range: %d Delta: %d\n",
//			PIRSense,PIRMin,PIRMax,PIRRange,PIRDelta);

		PIRShift = (9 * PIRDelta)/PIRRange;
		LEDBits = 0x00001 << PIRShift;
		SetBarBits(LEDBits);

		sprintf(LEDCharBuffer,"%4d",PIRSense);
		WriteLEDString(LEDCharBuffer);

		digitalWrite(PIN_HEARTBEAT,LOW);

		MillisThen = MillisNow;
	}

}
About these ads