Low Bat Powered 3 in 1 sensor Please Help
-
Hello:
I'm trying to build a very low powered ( 2 x AA ) Battery powered 3 in 1 sensor. ( Platform is a Pro Mini 8Mhz, 3.3v, with an PIR HC-SR501, NRF24L01, and a DHT 11 Temp and Hum sensor. The sensor should also report battery power periodically ideally using the vcc library.
The code below, primarily taken from here works well, thanks very much to @AWI sam-ple code
@AWI also mentions a great sketch here Good Example
And I really want to implement it, using the vcc library to report battery power, but am having a really hard time getting these sketched together. There are several good post about low power consumption, and combining gw.send functions in one burst at the end of the sketch to save power. Also, clocking the pro mini down to 1Mhz to save power, and only update the gateway when temp or humidity levels change by .5 - 1 deg.
Below is my code that is lacking a lot of this functionality. I've been trying for two weeks to try and implement this, but keep banging my head against the desk.... Is anyone able to help a newbie out? Thanks very much for any assistance.
#define MY_GW_ID 31 // Enable debug prints #define MY_DEBUG // Enable and select radio type attached #define MY_RADIO_NRF24 //#define MY_RADIO_RFM69 #include <SPI.h> #include <MySensor.h> #include <DHT.h> int BATTERY_SENSE_PIN = A0; // select the input pin for the battery sense point //Constants #define SKETCH_NAME "3 in 1 Sensor" #define SKETCH_VERSION "1.0" #define CHILD_ID_HUM 0 // Child id for Humidity #define CHILD_ID_TEMP 1 // Child id for Temperature #define CHILD_ID_MOT 2 // Id of the sensor child #define HUMIDITY_SENSOR_DIGITAL_PIN 7 // Where is my DHT22 data pin connected to #define DIGITAL_INPUT_SENSOR 3 // The digital input you attached your motion sensor. (Only 2 and 3 generates interrupt!) #define INTERRUPT DIGITAL_INPUT_SENSOR-2 // Usually the interrupt = pin -2 (on uno/nano anyway) //Misc. variables uint8_t switchState = 2; unsigned long SLEEP_TIME = 820000; // Sleep time between reads (in milliseconds) (close to 15') unsigned long SLEEP_TIME2 = 275000; // Sleep time after int. (in milliseconds) (close to 5') uint8_t cycleInProgress = 0; uint8_t firstReportDone = 0; uint8_t firstReportWithZeroDone = 0; int oldBatteryPcnt = 0; MySensor gw; DHT dht; float lastTemp = 0 ; float lastHum = 0 ; boolean lastTripped = false ; boolean metric = true; MyMessage msgHum(CHILD_ID_HUM, V_HUM); MyMessage msgTemp(CHILD_ID_TEMP, V_TEMP); MyMessage msgMot(CHILD_ID_MOT, V_TRIPPED); void setup() { // use the 1.1 V internal reference #if defined(__AVR_ATmega2560__) analogReference(INTERNAL1V1); #else analogReference(INTERNAL); #endif gw.begin(); dht.setup(HUMIDITY_SENSOR_DIGITAL_PIN); // Send the Sketch Version Information to the Gateway gw.sendSketchInfo("Humidity/Motion", "1.0"); pinMode(DIGITAL_INPUT_SENSOR, INPUT); // sets the motion sensor digital pin as input // Register all sensors to gw (they will be created as child devices) gw.present(CHILD_ID_HUM, S_HUM); gw.present(CHILD_ID_TEMP, S_TEMP); gw.present(CHILD_ID_MOT, S_MOTION); metric = gw.getConfig().isMetric; } void loop() { // get the battery Voltage int sensorValue = analogRead(BATTERY_SENSE_PIN); #ifdef DEBUG Serial.println(sensorValue); #endif // 1M, 470K divider across battery and using internal ADC ref of 1.1V // Sense point is bypassed with 0.1 uF cap to reduce noise at that point // ((1e6+470e3)/470e3)*1.1 = Vmax = 3.44 Volts // 3.44/1023 = Volts per bit = 0.003363075 float batteryV = sensorValue * 0.003363075; int batteryPcnt = sensorValue / 10; #ifdef DEBUG Serial.print("Battery Voltage: "); Serial.print(batteryV); Serial.println(" V"); Serial.print("Battery percent: "); Serial.print(batteryPcnt); Serial.println(" %"); #endif if (oldBatteryPcnt != batteryPcnt) { // Power up radio after sleep gw.sendBatteryLevel(batteryPcnt); oldBatteryPcnt = batteryPcnt; } // Read digital motion value boolean tripped = digitalRead(DIGITAL_INPUT_SENSOR) == HIGH; if (tripped != lastTripped ) { Serial.println(tripped); gw.send(msgMot.set(tripped?"1":"0")); // Send tripped value to gw } delay(dht.getMinimumSamplingPeriod()); float temperature = dht.getTemperature(); float humidity = dht.getHumidity(); if (isnan(temperature)) { Serial.println("Failed reading temperature from DHT"); } else if (temperature != lastTemp) { lastTemp = temperature; if (!metric) { temperature = dht.toFahrenheit(temperature); } gw.send(msgTemp.set(temperature, 1)); Serial.print("T: "); Serial.println(temperature); } if (isnan(humidity)) { Serial.println("Failed reading humidity from DHT"); } else if (humidity != lastHum) { lastHum = humidity; gw.send(msgHum.set(humidity, 1)); Serial.print("H: "); Serial.println(humidity); } if (oldBatteryPcnt != batteryPcnt) { // Power up radio after sleep gw.sendBatteryLevel(batteryPcnt); oldBatteryPcnt = batteryPcnt; } // Sleep until interrupt comes in on motion sensor. Send update every two minute. gw.sleep(INTERRUPT,CHANGE, SLEEP_TIME); }
-
@rhuehn hi maybe you can have a look at this sketch. https://github.com/sweebee/Arduino-home-automation/blob/master/MySensors 1.5/Arduino/multiSensor/multiSensor.ino This had been the basis for my multisensor as Well. The battery input is directly on vcc pin. And the voltage regulator and led are disabled on the pro mini. No need to attach additional resistor for measuring battery power. You can make the interval as long as you like for sending over. Info is only submitted if the value is changed. 1 MHz is optimal for power consumption. But i have not been able
To upload the sketch in that mode.
-
Hey @dynamite Thanks for the link. The sketch looks great, but I don't believe it deviates much from the sketch I have posted above. If we are trying to conserve utmost battery and only wake when the PIR triggers, or if there is a change in temp ( +/- a deg ), and then fire everything over in 1 burst, or alternatively every hour. Then look at possibly slowing down the pro mini to 1Mhz vs 8. I don't know what do you think?
-
@rhuehn Just a few things to consider if building an ultra low power sensor:
- The sensor has to work at low voltages (if using 2AA), so either change the power source to something that delivers > 3V (i.e. Lithium CR123(no rechargable) and/ or use sensors that are capable of low power operation.
- The DHT22 sensor is not very reliable at < 3.3V, consider Si7021/ SHT21 and use the I2C interface
- PIR HC-SR501 is standard a 5V sensor. You need to modify / connect it different to have it work at 3.3V
- The PIR will not allow for lower consumption that around 60uA (that is what it takes).
- Using the gw.sleep() with a timer value takes around 6uA , you can only go lower by using only interrupt.
- A pro-mini should be modified. At least remove/ disconnect the led and preferably the regulator.
- Burning a bootloader to run on 1 mHz will reduce the consumption by a few percent only when active (not in sleep). But keeps the arduino awake longer when a lot of processing is to be done. So just keep it at 8 mHz.
- Use sensor libraries that allow for low power. Sometimes there are long "delays()" when waiting for results. This will keep the arduino (and probably radio) active while doing nothing. [I still need to do some more research on this]
and:
- Put things into perspective.. not very much use to go for the last uA if your sensors take more that 50 uA (i.e. PIR).
- Be patient. 1 thing at a time. Start easy and observe the differences when you add more functionality.
next is your sketch...
-
@rhuehn Your sketch.. there is nothing wrong with these... not optimized, but on first glance these should work.
A few observations:
-
When you battery power the whole thing (without regulator) you can read the voltage without the external resistors. Use the vvc library. A great, effective and simple library to measure voltage by our fellow member @Yveaux. Simplifies the code too. Look at his thread
-
The line
delay(dht.getMinimumSamplingPeriod());
is a potential long wait time, depending on the library used. Try to move it to a position early in the sketch before the radio is alive. -
The line
gw.sleep(INTERRUPT,CHANGE, SLEEP_TIME);
will keep the arduino internal timer alive (> 5uA). But why bother if you use a PIR use:
gw.sleep(INTERRUPT1, CHANGE, 0 );
if you want to have deep sleep. -
Try to wake the radio at the end of the sketch. So read the sensors, save the values and send in the end. (you can move the "if's" i.e
if (oldBatteryPcnt != batteryPcnt) {
to the end to)
A long post (maybe not relevant) being typed by consultant (me )... before asking the question... "What is the problem, in what are you not succeeding?"
-
-
@AWI for my understanding in case I do keep the voltage regulator and feed my pro mini 3v with the 3 AAA of the Ikea PIR. Will it keep working even of the voltage drops to 2v. (Of course i need to change the bootloader.) can i also use the simple battery level routine? The power at the vcc will be 3v or lower even if I feed it with 3 batteries?
-
@dynamite not sure if I understand your question. If you use a regulator you need to measure the battery voltage by measuring on an analog input.
Your arduino probably won't work below 2.3 volts when powered from the raw pin. The LDO voltage drop would cause this.
-
My apologies for the delay in this reply as I was travelling and did not have access to the net... Thank you both for the replies, I just got back and couldn't wait to reply:
@AWI I will comment on the items below from your first post, below:
PIR HC-SR501 is standard a 5V sensor. You need to modify / connect it different to have it work at 3.3V
This has been modified to work @ 3.3VThe PIR will not allow for lower consumption that around 60uA (that is what it takes).
Using the gw.sleep() with a timer value takes around 6uA , you can only go lower by using only interrupt.
I believe I need to look at this at this seems to be a better option, and more suitable for the applicationA pro-mini should be modified. At least remove/ disconnect the led and preferably the regulator.
These have also been modified as mentioned by yourself.Burning a bootloader to run on 1 mHz will reduce the consumption by a few percent only when active (not in sleep). But keeps the arduino awake longer when a lot of processing is to be done. So just keep it at 8 mHz.
Sounds like a great idea, I will keep it as isI believe @dynamite is referencing this post above, and BTW @dynamite I want to do the EXACT same thing:
So a few things, based on that Ikea module, We are trying to acheive the lowest power consumption from either 2 or 3 X AA Batteries. modified to work with perhaps a PIR HC-SR501 ( discarding the original ), along with a DHT11 ( or 22 ), and report battery levels, and send the information back to a gateway, perhaps every hour unless the temp changes +/- 1 degree, or the PIR wakes, causing an action to be triggered, then go back to deep sleep.
I tried a few modification to your suggestions, but lost functionality with the PIR, and battery reporting. My sketch is below, and not working very well:
#define MY_NODE_ID 31 // Enable debug prints #define MY_DEBUG // Enable and select radio type attached #define MY_RADIO_NRF24 //#define MY_RADIO_RFM69 #include <SPI.h> #include <MySensor.h> #include <DHT.h> #include <Vcc.h> //Constants #define SKETCH_NAME "3 in 1 Sensor" #define SKETCH_VERSION "1.0" #define CHILD_ID_HUM 0 // Child id for Humidity #define CHILD_ID_TEMP 1 // Child id for Temperature #define CHILD_ID_MOT 2 // Id of the sensor child #define HUMIDITY_SENSOR_DIGITAL_PIN 7 // Where is my DHT22 data pin connected to #define DIGITAL_INPUT_SENSOR 3 // The digital input you attached your motion sensor. (Only 2 and 3 generates interrupt!) #define INTERRUPT DIGITAL_INPUT_SENSOR-2 // Usually the interrupt = pin -2 (on uno/nano anyway) //Misc. variables uint8_t switchState = 2; unsigned long SLEEP_TIME = 820000; // Sleep time between reads (in milliseconds) (close to 15') unsigned long SLEEP_TIME2 = 275000; // Sleep time after int. (in milliseconds) (close to 5') uint8_t cycleInProgress = 0; uint8_t firstReportDone = 0; uint8_t firstReportWithZeroDone = 0; const float VccExpected = 3.0; const float VccCorrection = 2.860/2.92; // Measured Vcc by multimeter divided by reported Vcc Vcc vcc(VccCorrection); static int oldBatteryPcnt = 0; MySensor gw; DHT dht; float lastTemp = 0 ; float lastHum = 0 ; boolean lastTripped = false ; boolean metric = true; MyMessage msgHum(CHILD_ID_HUM, V_HUM); MyMessage msgTemp(CHILD_ID_TEMP, V_TEMP); MyMessage msgMot(CHILD_ID_MOT, V_TRIPPED); void setup() { gw.begin(); dht.setup(HUMIDITY_SENSOR_DIGITAL_PIN); // Send the Sketch Version Information to the Gateway gw.sendSketchInfo("3 in 1 Sensor", "1.0"); pinMode(DIGITAL_INPUT_SENSOR, INPUT); // sets the motion sensor digital pin as input // Register all sensors to gw (they will be created as child devices) gw.present(CHILD_ID_HUM, S_HUM); gw.present(CHILD_ID_TEMP, S_TEMP); gw.present(CHILD_ID_MOT, S_MOTION); metric = gw.getConfig().isMetric; } void loop() { // Read digital motion value boolean tripped = digitalRead(DIGITAL_INPUT_SENSOR) == HIGH; if (tripped != lastTripped ) { Serial.println(tripped); gw.send(msgMot.set(tripped?"1":"0")); // Send tripped value to gw } delay(dht.getMinimumSamplingPeriod()); float temperature = dht.getTemperature(); float humidity = dht.getHumidity(); if (isnan(temperature)) { Serial.println("Failed reading temperature from DHT"); } else if (temperature != lastTemp) { lastTemp = temperature; if (!metric) { temperature = dht.toFahrenheit(temperature); } gw.send(msgTemp.set(temperature, 1)); Serial.print("T: "); Serial.println(temperature); } if (isnan(humidity)) { Serial.println("Failed reading humidity from DHT"); } else if (humidity != lastHum) { lastHum = humidity; gw.send(msgHum.set(humidity, 1)); Serial.print("H: "); Serial.println(humidity); } // Read Battery Value int batteryPcnt = (int)vcc.Read_Perc(VccExpected); if (oldBatteryPcnt != batteryPcnt) { gw.sendBatteryLevel(batteryPcnt); oldBatteryPcnt = batteryPcnt; } // Sleep until interrupt comes in on motion sensor. Send update every two minute. gw.sleep(DIGITAL_INPUT_SENSOR, CHANGE, 0 ); }
-
@rhuehn At first glance..
int batteryPcnt = (int)vcc.Read_Perc(VccExpected);
needs more arguments, try
int batteryPcnt = (int)vcc.Read_Perc(VccMinimal, VccMaximal);
where VccMaximal / VccMinimal is the highest/lowest battery level expected ... For a DHT sensor this would be around 3 volts.. which makes this sensor less interesting for low power applications...
-
Thanks @AWI I have modified the sketch, and everything below seems to be working ok. If you can look at it, and post a comment if you think it is the most optimal for power use, and the vcc readings are correct it would be greatly appreciated. The complete sketch is below. I will likely look at changing the DHT's to HTU21 in the future. Is there a low powered PIR that you know of?:
#define MY_GW_ID 31 // Enable debug prints #define MY_DEBUG // Enable and select radio type attached #define MY_RADIO_NRF24 //#define MY_RADIO_RFM69 #include <SPI.h> #include <MySensor.h> #include <DHT.h> #include <Vcc.h> //Constants #define SKETCH_NAME "3 in 1 Sensor" #define SKETCH_VERSION "1.0" #define CHILD_ID_TEMP 1 // Child id for Temperature #define CHILD_ID_HUM 0 // Child id for Humidity #define CHILD_ID_VOLT 3 // Child id for battery reading #define CHILD_ID_MOT 2 // Id of the sensor child #define HUMIDITY_SENSOR_DIGITAL_PIN 7 // Where is my DHT22 data pin connected to #define DIGITAL_INPUT_SENSOR 3 // The digital input you attached your motion sensor. (Only 2 and 3 generates interrupt!) #define INTERRUPT DIGITAL_INPUT_SENSOR-2 // Usually the interrupt = pin -2 (on uno/nano anyway) int node_id=31; // What is my node id //Misc. variables unsigned long SLEEP_TIME =60000UL; // Sleep time between reads (in milliseconds) int sleepcycle=1; // Counter to count the amout of time not sending data int humoffset=2; // only data is send if humidity is changed for this amout int tempoffset=1.0; // only data is if temperature is changed for this amout int gwsendtimout=20; // each 20*sleep_time (20 minutes) data will be send const float VccMin = 1.7; // Minimum expected Vcc level, in Volts. const float VccMax = 3.1; // Maximum expected Vcc level, in Volts. const float VccCorrection = 1.0/1.0; // Measured Vcc by multimeter divided by reported Vcc Vcc vcc(VccCorrection); MySensor gw; DHT dht; //Store last values float lastTemp = 0 ; float lastHum = 0 ; float batteryV=0; int oldBatteryPcnt = 0; boolean lastTripped = false ; boolean metric = true; boolean gwsend = true; // to determin if data has to be send MyMessage msgHum(CHILD_ID_HUM, V_HUM); MyMessage msgTemp(CHILD_ID_TEMP, V_TEMP); MyMessage msgVolt(CHILD_ID_VOLT, V_VOLTAGE); MyMessage msgMot(CHILD_ID_MOT, V_TRIPPED); void setup() { gw.begin(NULL,node_id,false); dht.setup(HUMIDITY_SENSOR_DIGITAL_PIN); // Send the Sketch Version Information to the Gateway gw.sendSketchInfo("3 in 1 Sensor", "1.0",true); pinMode(DIGITAL_INPUT_SENSOR, INPUT); // sets the motion sensor digital pin as input // Register all sensors to gw (they will be created as child devices) gw.present(CHILD_ID_HUM, S_HUM,"Kitchen Humidity"); gw.present(CHILD_ID_TEMP, S_TEMP,"Kitchen Temperature"); gw.present(CHILD_ID_VOLT, S_MULTIMETER,"Kitchen Battery Voltage"); metric = gw.getConfig().isMetric; } void loop() { // get the battery Voltage batteryV = vcc.Read_Volts(); int batteryPcnt = vcc.Read_Perc(VccMin, VccMax); if (oldBatteryPcnt != batteryPcnt) { // Power up radio after sleep gwsend=true; oldBatteryPcnt = batteryPcnt; } delay(dht.getMinimumSamplingPeriod()); float temp1 = dht.getTemperature(); float humidity = dht.getHumidity(); if (isnan(temp1)) { // Serial.println("Failed reading temperature from DHT"); } else if ((temp1 <= lastTemp-tempoffset)||(temp1 >= lastTemp+tempoffset)) { lastTemp = temp1; if (!metric) { temp1 = dht.toFahrenheit(temp1); } gwsend=true; } if (isnan(humidity)) { // Serial.println("Failed reading humidity from DHT"); } else if ((humidity <= lastHum-humoffset)||(humidity >= lastHum+humoffset)) { lastHum = humidity; gwsend=true; } if (sleepcycle>gwsendtimout){ gwsend=true; } // Read digital motion value boolean tripped = digitalRead(DIGITAL_INPUT_SENSOR) == HIGH; if (tripped != lastTripped ) { Serial.println(tripped); if (gwsend){ gw.sendBatteryLevel(oldBatteryPcnt); gw.send(msgVolt.set(batteryV, 1)); gw.send(msgTemp.set(lastTemp, 1)); gw.send(msgHum.set(lastHum, 1)); gw.send(msgMot.set(tripped?"1":"0")); // Send tripped value to gw gwsend=false; sleepcycle=1; } sleepcycle++; gw.sleep(INTERRUPT, CHANGE, 0 ); } }
Thanks again
-
@rhuehn your sketch looks good. A small optimization can be made by sending the individual values only if they changed.
I don't have a low power alternative for the pir but there are some threads in this forum discussing these.
Have you measured the consumption of your circuit? That would be the best way to go forward. You don't need an expensive meter to get the grabs
-
Thanks @AWI for the reply. I thought I was sending values based on the above code with the following:
//Misc. variables unsigned long SLEEP_TIME =60000UL; // Sleep time between reads (in milliseconds) int sleepcycle=1; // Counter to count the amout of time not sending data int humoffset=2; // only data is send if humidity is changed for this amout int tempoffset=1.0; // only data is if temperature is changed for this amout int gwsendtimout=20; // each 20*sleep_time (20 minutes) data will be send
The Temp and Humidity in the sketch is quite honestly of lower importance to me as the PIR. I believe ( please correct me if I am wrong ), but the sensor should only report every 20 minutes, and only if a temp changes by a degree or humidity changes by 2 correct?
The PIR, and sensor should sleep as often as possible, but based on motion in the vicinity of the sensor, should trigger an action immediately, and based on that, could send the temp and hum readings at the same time, OR every 20 minutes if there is a change...
Thanks @AWI comments welcome.