This project started as a simple hypothesis:
“Could I, using the Particle Photon, to monitor a wood-stove for a winter season running on two AA lithium batteries.“
Recharchable batteries are cool but they just don’t nearly have the energy density as primary cells. A hack that can be used to extend the life of any battery powered application is to power everything down (to an off state) and use an RTC (Real time clock) timer which would “wake-up” the device when it was time to make a measurement.
I created a mish-mash architecture document using draw.io which is like Omnigraffle on steroids (and can be used in-browser to boot!)
I used the following dev-boards to test my hypothesis.
Most are Digikey links because their mindbendingly fast warehouse. I can consistently get things in two days if I order before 8p CT. I don’t even bother half the time ordering directly from Sparkfun or Adafruit because they’re just too darn slow.
Two short days later after ordering, wiring them together took less than an hour and I was compiling some Hello World code within that same timeframe. It’s probably the fastest I’ve gotten functionality out of so many devices at the same time. (Kudos to the Particle team for that!!!)
One of the reasons I was so speedy was because of the prototyping techniques used. As you can see above, I utilized both wire-wrap and a solder-less breadboard to make all the connections. I love wire-wrap enough that I wrote up a guide about it over at Circuit Dojo. Needless to say, you should get familiar with both if you haven’t already!
The biggest thing to get working was the I2C and searching for the correct addresses in the data sheets was half the battle.
#define RTC_ADDRESS 0x51
#define SI7021_ADDRESS 0x40
#define MCP9600_ADDRESS 0x60
#define DISPLAY_ADDRESS 0x70
After figuring out what the address was for the Si7021 (humidity + ambient temp), only two commands have to be issued in order to get both ambient temperature and relative humidity readings.
Here’s the exact code I was using
uint16_t get_onboard_temp() {
// Si7021 Temperature
Wire.beginTransmission(SI7021_ADDRESS);
Wire.write(SI7021_TEMP_READ_CMD); // sends one byte
Wire.endTransmission(); // stop transaction
Wire.requestFrom(SI7021_ADDRESS, 2);
// Serial1.print("temp:");
uint16_t temp_code = (Wire.read() & 0x00ff) << 8 | (Wire.read() & 0x00ff);
uint16_t temp = ((175.72 * temp_code) / 0xffff - 46.85) * 100;
// Serial1.printf("%dC", temp); // print the temperature
return temp;
}
uint16_t get_onboard_humidity() {
// Si7021 Humidity
Wire.beginTransmission(SI7021_ADDRESS);
Wire.write(SI7021_HUMIDITY_READ_CMD); // sends one byte
Wire.endTransmission(); // stop transaction
Wire.requestFrom(SI7021_ADDRESS, 2);
// Serial1.print("\nhumidity:");
uint16_t rh_code = (Wire.read() & 0x00ff) << 8 | (Wire.read() & 0x00ff);
uint16_t rh = ((125 * rh_code) / 0xffff - 6) * 100;
// Serial1.printf("%d%%", rh); // print the humidity
return rh;
}
What’s handy for each of them is that the chip uses clock stretching to hold the processor in place until the measurements are ready. That way there’s no polling or interrupts to worry about. The downside is that it’s blocking (i.e. the Particle technically can’t run any of your code while it waits) but it doesn’t matter in such simple project such as this!
The thermocouple IC was similarly organized. It required a read first to make sure the data was available and then another read of the compensated “hot” register.
float get_thermocouple_temp() {
// MCP9600 Temperature
Wire.beginTransmission(MCP9600_ADDRESS);
Wire.write(MCP9600_STATUS_REGISTER_CMD); // sends one byte
Wire.endTransmission(); // stop transaction
Wire.requestFrom(MCP9600_ADDRESS, 1);
uint8_t status = Wire.read();
// Check to make sure the sample is ready
if (status & MCP9600_UPDATE_AVAIL_BITMAP) {
// Serial1.println("Sample is ready");
} else {
Serial1.println("Error: Sample is NOT ready");
}
// MCP9600 Temperature
Wire.beginTransmission(MCP9600_ADDRESS);
Wire.write(MCP9600_TEMP_HOT_CMD); // sends one byte
Wire.endTransmission(); // stop transaction
Wire.requestFrom(MCP9600_ADDRESS, 2);
// Serial1.print("\nt-temp:");
uint8_t upper = Wire.read();
uint8_t lower = Wire.read();
float temp = 0;
// Depending on if its positive or negative
if ((upper & 0x80) == 0x80) {
temp = ((upper * 16 + lower / 16.0) - 4096);
} else {
temp = (upper * 16 + lower / 16.0);
}
// Serial1.printf("%f\n", temp);
return temp;
}
I did upload the code thus far to Github here. For those who are interested I also made a separate branch, called powertest
for the power measurements I made to test out my hypothesis. I even created a power model to do the calculations to get a more definitive yes/no answer.
I’ll probably play around with this a bit more as I think it’s still doable. Just need to figure out what needs to be sacrificed to get that ~200 days of use. I did go into more detail on my blog if you’re interested to see how I tested it.