Introduction: IOT123 - SR04 DISTANCE TRIGGER BRICK

About: The tension between novelty and familiarity...

The IOT123 BRICKS are DIY modular units that can be mashed up with other IOT123 BRICKS, to add functionality to a node or wearable. They are based on the inch square, double-sided protoboards with interconnected through holes.

In developing an OLED ID Tag, I needed to make my design as energy efficient as possible. One approach was to only display the tag if there was someone nearby (think ComiCon or Exhibition booths). The sensors themselves can draw a lot of current, so this needed to be optimized. Originally I started with a Sharp IR sensor, but bricked my only one during a development spike.

I believe there are many scenarios where low power sensing is required, so I made the design fairly generic where the hardware sensor and the associated code could be swapped out easily. Also serial pins (TX/RX) have been broken out so as to debug the ATTINY85 during development. When a predefined (runtime assignable via a momentary button) value is reached, a pin is sent HIGH, to wake the HOST MCU. When not triggering/sensing, the ATTINY/SR04 are sleeping/powered down.

The mashup has been coined a BRICK as a nod to the ubiquitous Keyes bricks.

Step 1: Materials and Tools

This design can be packaged any way you like but I suggest you use the 1" format protoboards to take advantage of the future modules.

There is a full Bill of Material and Sourcing list.

  1. SR04 Ultrasonic rangefinder (1)
  2. 1" Double sided protoboard (1)
  3. 8 Pin DIL IC Socket (optional)
  4. ATTINY85 20PU (1)
  5. 2N7000 mosfet (1)
  6. Hookup wire (~10)
  7. 10K resistors (2)
  8. 1K resistor (1)
  9. Male header right angled (5P)
  10. Solder Iron (1)
  11. Solder (1)

Step 2: Program the ATtiny85

ATTINY85 checks the current distance of objects to a SR04 Ultrasonic range finder against a user configurable value. If closer, a pin is set HIGH to wake a HOST MCU, then sleeps for 1 second. If not closer, just sleeps for a second. Repeat.

// ATTINY85 / 1MHz
#include<EEPROM.h>
#include<avr/sleep.h>
#include<avr/wdt.h>
#defineUSE_SOFTWARE_SERIAL1
#if (USE_SOFTWARE_SERIAL)
#include<SoftwareSerial.h>
#defineRX0
#defineTX1
SoftwareSerial Serial(RX, TX);
#endif
#ifndef cbi
#definecbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#definesbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
volatile boolean _f_wdt = 1;
constint _triggerPin = 0;
constint _butPin = A0;
constint _powerPin = 2;
constint _userInputDelay = 10000; // 10s
constint _userInputDefault = 2;
int _userInputValue;
int _triggerDistance = -1;
voidsetup() {
#if (USE_SOFTWARE_SERIAL)
Serial.begin(9600);
#endif
setupWatchdog(6); // approximately 1 seconds sleep
initPins();
delay(100);
serialPrintLn("setup");
_userInputValue = getUserValue(); // reads button presses for 10s
serialPrintLn("USER VALUE: " + String(_userInputValue));
}
voidloop() {
setPower(true);
delay(100);
readSensorCheckTrigger();
setPower(false);
systemSleep();
resetPorts();
}
voidserialPrintLn(String text){
#if (USE_SOFTWARE_SERIAL)
Serial.println(text);
#endif
}
voidinitPins(){
pinMode(_powerPin,OUTPUT);
digitalWrite(_powerPin,LOW);
pinMode(_triggerPin, OUTPUT);
digitalWrite(_triggerPin,LOW);
delay(100);
pinMode(_triggerPin,INPUT);
}
// counts button presses for 10s, or gets previously set value from EEPROM
intgetUserValue(){
int butPresses = 0;
int butState = 0;
int lastButState = 0;
unsignedlong initMillis = millis();
while (true){
butState = getResetButtonState();
if (butState != lastButState) {
if (butState == HIGH) {
butPresses++;
serialPrintLn("press");
}
delay(100);
}
lastButState = butState;
unsignedlong curMillis = millis();
if(curMillis - initMillis > _userInputDelay) {
break;
}
}
if (butPresses == 0){
butPresses = EEPROMReadInt(0);
if (butPresses == -1){
butPresses = _userInputDefault;
}
}else{
if (butPresses >10){
butPresses = 10;
}
EEPROMWriteInt(0, butPresses);
}
return butPresses;
}
// explanation: https://electronics.stackexchange.com/questions/195146/use-the-reset-pin-on-attiny85-as-input-with-voltage-divider
intgetResetButtonState(){
return (analogRead(_butPin) >1000 ) ? 0 : 1;
}
voidEEPROMWriteInt(int address, int value)
{
byte two = (value & 0xFF);
byte one = ((value >>8) & 0xFF);
EEPROM.update(address, two);
EEPROM.update(address + 1, one);
}
intEEPROMReadInt(int address)
{
long two = EEPROM.read(address);
long one = EEPROM.read(address + 1);
return ((two << 0) & 0xFFFFFF) + ((one << 8) & 0xFFFFFFFF);
}
// toggle low side power switch: https://www.electronics-tutorials.ws/transistor/tran_7.html
voidsetPower(bool on){
if (on){
pinMode(_powerPin,OUTPUT);
digitalWrite(_powerPin,HIGH);
}else{
digitalWrite(_powerPin,LOW);
delay(100);
pinMode(_powerPin,INPUT);
}
}
// 0=16ms, 1=32ms,2=64ms,3=128ms,4=250ms,5=500ms
// 6=1 sec,7=2 sec, 8=4 sec, 9= 8sec
voidsetupWatchdog(int ii) {
byte bb;
int ww;
if (ii >9 ) ii=9;
bb=ii & 7;
if (ii >7) bb|= (1<<5);
bb|= (1<
ww=bb;
MCUSR &= ~(1<
// start timed sequence
WDTCR |= (1<1<
// set new watchdog timeout value
WDTCR = bb;
WDTCR |= _BV(WDIE);
}
// Watchdog Interrupt Service / is executed when watchdog timed out
ISR(WDT_vect) {
_f_wdt=1; // set global flag
}
// set system into the sleep state
// system wakes up when wtchdog is timed out
voidsystemSleep() {
cbi(ADCSRA,ADEN); // switch Analog to Digitalconverter OFF
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here
sleep_enable();
sleep_mode(); // System sleeps here
sleep_disable(); // System continues execution here when watchdog timed out
sbi(ADCSRA,ADEN); // switch Analog to Digitalconverter ON
}
voidsetTrigger(){
serialPrintLn("setTrigger...");
pinMode(_triggerPin,OUTPUT);
digitalWrite(_triggerPin,HIGH);
delay(1000);
digitalWrite(_triggerPin,LOW);
delay(100);
pinMode(_triggerPin,INPUT);
}
voidresetPorts(){// set all ports into state before sleep
pinMode(_triggerPin,OUTPUT);
pinMode(_powerPin,OUTPUT);
}
//--------------------------------THE FOLLOWING CODE IS SPECIFIC TO THE CURRENT SENSOR
constint _sr04TriggerPin = 3;
constint _sr04EcchoPin = 4;
voidreadSensorCheckTrigger(){
constint min = 10;
constint max = 400;
if (_triggerDistance == -1){
_triggerDistance = map(_userInputValue, 1, 10, min, max);
serialPrintLn("TRIGGER DISTANCE: " + String(_triggerDistance) + "cm");
}
long distance = getCentimeters();
serialPrintLn("CURRENT DISTANCE: " + String(distance) + "cm");
if (distance < _triggerDistance){
setTrigger();
}
}
longgetCentimeters(){
long duration, cm;
float current_time=0;
// The sensor is triggered by a HIGH pulse of 10 or more microseconds.
// Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
pinMode(_sr04TriggerPin, OUTPUT);
digitalWrite(_sr04TriggerPin, LOW);
delayMicroseconds(2);
digitalWrite(_sr04TriggerPin, HIGH);
delayMicroseconds(10);
digitalWrite(_sr04TriggerPin, LOW);
// Read the signal from the sensor: a HIGH pulse whose
// duration is the time (in microseconds) from the sending
// of the ping to the reception of its echo off of an object.
pinMode(_sr04EcchoPin, INPUT);
duration = pulseIn(_sr04EcchoPin, HIGH);
// get the current time in milliseconds since the program started
current_time = millis();
// convert the time into a distance
cm = microsecondsToCentimeters(duration);
return cm;
}
longmicrosecondsToCentimeters(long microseconds)
{
// The speed of sound is 340 m/s or 29 microseconds per centimeter.
// The ping travels out and back, so to find the distance of the
// object we take half of the distance travelled.
return microseconds / 29 / 2;
}

Step 3: Assembling the Generic Circuit

This design is meant for any sensors that provide analog values and can sleep periodically. Therefore the generic assembly is detailed separately.

There are a few occasions where soldering on the other side of a through hole is obstructed. When this is the case, I soldered a dob on the target through hole, then from the side melt the solder and push the exposed hookup wire into center hole, hold and remove heat.

  1. On the rear, insert the momentary button, DIL IC Socket, 2N7000 (leave outer leg long), and male headers and solder off on front side.
  2. On the rear, thread a stripped red hookup wire through RED2, into RED1 on the front and solder off. Trace the other side into RED3 and solder.
  3. On the rear, with black hookup wire, trace and solder BLACK1 to BLACK2.
  4. On the front, bend the leg of BLACK3 to BLACK4 and solder.
  5. On the front, with yellow hookup wire, trace and solder YELLOW1 to YELLOW2, YELLOW3 to YELLOW4, YELLOW5 to YELLOW6.
  6. On the front, with a 1K resistors, trace and solder GREY1 to GREY2.
  7. On the front, with 10K resistors, trace and solder GREY3 to GREY4 and GREY5 to GREY6.

Step 4: Assembling the SR04 Circuit

These steps can be mixed-in with the above step as you are dealing with the RED/YELLOW wires. The steps have been structured like this for modularity.

  1. On the rear, with red hookup wire, trace and solder RED4 to RED5.
  2. On the front, with yellow hookup wire, trace and solder YELLOW7 to YELLOW8 and YELLOW9 to YELLOW10.
  3. On the front, insert SR04.Gnd into ORANGE1, SR04.Eccho into ORANGE2, SR04.Trig into ORANGE3, SR04.Vcc into ORANGE4 and solder.

Step 5: Testing the Circuit

The trigger distance is set during the first 10 seconds on boot (with the momentary button) and used on reboot if not changed. The range is divided by 10 and each button press starts at the minimum and increases with these units. 1 press is about 10cm, 2 presses 50cm, 3 presses 90cm and so on.

Using Software Serial:

  1. Use the hookup guide on TTL-232 Jig above.
  2. Follow instructions for using SoftwareSerial.
  3. Watch the video: SR04 TRIGGER BRICK CONSOLE OUTPUT.

Using a LED Indicator for the Trigger:

  1. Use the breadboard layout above.
  2. Watch the video: SR04 TRIGGER BRICK LED OUTPUT.

Step 6: Next Steps

  • The position of the HOST MCU connections can be varied to anywhere on the board where the circuit still follows the schematic.
  • The SERIAL connections are not needed at all.
  • The position of the SENSOR connections can be varied to anywhere on the board where the circuit still follows the schematic.

  • Try the generic circuit with other sensors that seem a good fit.