First off, this Design Challenge has been a blast. I actually had to dust out the mental cobwebs and derive some equations because I hooked up my circuits differently than those on the internet examples. I truly feel less foggy-brained. I also had some epiphanies during the process. Good-bye COVID brain. You will not be missed.
Since the Design Challenge was Experimenting with Thermistors, I should talk to those first. Thank you Molex and e14 for letting me play along. I will admit to buying a second kit because it was affordable and I fully expected to destroy something in the process. I was wrong. Everything survived. The one thing that didn't work as expected was my Arduino data logger. This is heartbreaking since this has been my most favorite Arduino shield ever. I may need therapy after this.
Step #1 was to create the sensor nodes. Each node consisted of a Raspberry Pi Pico, a nRF24L01+ transceiver and a thermistor set up in a voltage divider arrangement on Analog Input A0. I soldered Dupont cables onto each thermistor in an attempt to keep the Pico out of the direct heat path on the output of the miners – and all sensor nodes should be assembled the same. This modification proved to introduce additional resistance into the sensing circuit that impacted the temperature conversion (Steinhart-Hart equation). I was able to compensate enough by adjusting some of the variables in the Steinhart equation. Still on the “To Do” list is to introduce a variable resistor into the sensing circuit to see if it can be used to “calibrate” the final reading. That wasn't part of the original plan but I still want to try it.
The thermistors behaved as expected. I used the three ring terminal thermistors and the last two on the list due their higher internal resistance. I pondered why someone would choose a 3KΩ thermistor over a 47KΩ thermistor. Of course, I could not find anything that addressed this on the Internet. Anyway, my best guess is the slightly higher current draw will provide more consistent internal heating in lower temperatures than the lower current of the higher resistance thermistor.
Wait a minute!! Internal heating is a BAD thing! Is it? Based on my reading, it is supposed to be insignificant. Based on my multimeter, it's definitely “a thing”. BUT... once the thermistor is heated up, its performance is quite stable. This led to an epiphany. If I leave the sensing circuit energized all the time, I get more consistent readings. I had contemplated using a digital output to energize the circuit – to save on battery, but after watching the internal resistance change with just my multimeter attached to the thermistor... I wanted a better choice. For my application, the current draw was acceptable.
I mentioned having a few epiphanies along the way. My first epiphany was that temperature changes within my test environment (aka my building) were S L O W. Given my challenge with my Arduino data logger, I abdicated to the tried and true method of pen and paper. Recording the data from the screen, even every 10 minutes, quickly proved itself to be redundant. Hourly readings produced small changes that were worthy of recording but even those were highly dependent upon the ambient temperature. Another epiphany was that the actual temperature was immaterial. What I was trying to determine in the primary experiment was all about a change in temperature. I could have just used the analogRead(A0) values and arrived at the same conclusion. I am glad that I took the time to try to get somewhat accurate temperature readings out of the sensor nodes because I feel that it helped me understand the flow of heat better.
Back to the experiment plan. The original plan was to map the heat dispersion pattern out of the miners, horizontally and vertically. Ug! I shouldn't have been so quick to cut my PVC. I opted to do the vertical later, after seeing if I could determine the heat distribution patterns within the bay. I deployed my sensor nodes at the input (#1) and output (#2) of the miners, near three of the four ceiling fans and at the exhaust fan (#6). Since the wireless setup only supported five nodes, I took the old school route of pencil and paper to record the node at Location #4. It was also the node closest to my office.
Before this experiment, we had tried a number of different configurations to cool the building – including opening up the overhead doors during business hours. Here's another epiphany. If you do that, every flying insect within a 100 mile radius will flock to your building and die inside. You don't see them in my pictures because I vacuumed before snapping the picture. In three days, there will be thousands of dead gnats at the base of the walls.
So what did the numbers reveal?
First, the miners add about 14 degrees F all the time. It doesn't matter if the ambient is 50F or 90F. I did not push the high temperature limits too much as the miners throttle as they get hot and with Bitcoin being down... no sense in making a bad situation worse.
With all the doors closed and the ceiling fans off, the exhaust fan drew air in from the window in the Southwest corner (upper right on the building diagram). The miner fans pulled that air towards them and through the miners. The exiting air heated up the Northeast zone of the building (lower left on the building diagram) that was subsequently drawn off by the exhaust fan in the adjoining room and sent back outside. Nodes #4 and #5 we slightly cooler (3 degrees F on average) than the intake air at Node #1.
With all the doors closed and the ceiling fans on, Nodes #1, #4 and #5 were essentially at the same temperature. This is good information for the Winter when I want the heat to be distributed around the building. It's a little counter intuitive that parts of the bay are cooler with the fans off. This remained true when I moved to opening doors to let more ambient air in.
With the South door (near the open window) open and the ceiling fans off, the heat flow pattern remained essentially the same as with the door closed. The temperature at Node #4 dropped ever so slightly (1 to 2 degrees F) and the overall system temperatures dropped closer to outside ambient, but we're still only talking about 1 to 3 degrees F. This surprised me. If the wind blew from the South, things got slightly better – but it also rattled all of the training certificates hung on the Western wall. I did lose three frames during one good gust.
I was most curious to see what opening the North door would do for the heat dissipation. After all, it's downstream from the miners but it's close enough that you feel the heat when you use the door. Having the North door open did not change the temperatures at Nodes #1, #4 and #5 but it changed that Northeast zone (Node #3) by 5 degrees F. I should also mention that this zone hosts our company workout equipment. VERY few people have used this equipment since we added the noise and heat. Anyway, stripping away the heat after the fact works. Given this, I decided not to try the overhead door. Also see the note about all the dead gnats above.
The final test was to track the building temperature without the exhaust fan and with the doors closed. Another power outage provided the opportunity. The exhaust fan has to be manually reset if it loses power. Nodes #1 to #5 rose in temperature about 1 degree F per half hour for six hours. Interestingly, the point temperatures at the nodes stayed the same relative to each other. Everything was just getting slowly hotter. The one outlier was, of course, Node #6, the exhaust fan. With nothing to draw the hot air into the second room, its temperature was essentially the same as Node #4 – near the open window. When Node #2 hit 97 degrees F and it was already 2:30pm, I chose to turn the exhaust fan on. The temperatures in the building only dropped one degree by the time I left at 5pm. To me, that translates to a lot of heat stored up at the ceiling.
Back to the heat dispersion pattern. I left Node #2 and #3 in place and relocated Nodes #1, #5 and #6 to be in line with Node #3 so the line of sensors was perpendicular to the flow of air from the miners and to the exhaust fan. The sensor nodes had approximately 2 meter spacing. See the building diagram to the right. With the exhaust fan on, the two center nodes were only one degree F higher in temperature, but they were consistently higher. With the exhaust fan turned off, the temperature was highest near the wall and each node dropped by one degree F going into the building. No dispersion tests were done with the ceiling fans on.
The vertical "pattern" threw me the most surprises. I started the horizontal pattern tests (Yes. The horizontal tests.) with all five nodes in a row behind the miners to verify that they were reading somewhat alike. When I got to the one closest the wall, I felt something cold on the top of my head. It was a colder than normal day for the Summer but this was the hot zone. Yeah... I had been so focused on the heat generated by the Bitcoin miners that I had completely forgotten about the two Ethereum miners: Ten fans arranged horizontally that also blow air from one side to the other. But these miners don't really add heat. The base of the Ethereum miners also happens to be mounted about an inch above my head. There was a layer of cold air above all that hot air! The craziest part was that I could sense the temperature difference with just my hand for almost 3 meters from the miners - and it stayed at roughly the same height as it was created! I was not expecting that.
My readings/measurements while trying to test the vertical pattern somewhat near the miners were all over the place. The miners throttled their fans in addition to everything else. I was literally chasing the wind. So... I opoted to go with a single stack of sensors in the (approximate) center of the Northeast zone; the gym (the blue/yellow star on the building diagram). One sensor was placed next to a sprinkler head at 4.25 meters high. The next sensors were at 3 meters, 2.3 meters and 1.8 meters (corresponding to the rungs on my step ladder). As the height rose... so did the temperature - except the top sensor. The top sensor read the same as the sensor at 2.3 meters - every time. So much for worrying about setting off the sprinkler heads due to a rise in heat. Baffling. I guess the Ethereum miners were still influencing the flow.
Of my original questions, which ones are still unanswered? Do the different sensors give different readings in the same location? Yes. +/– 1 degree F, but that took some experimentation to try to get them to the point where they agreed with each other. It has less to do with the thermistors themselves than it does with the sensing circuit.
Can I use the temperature sensors to determine if the exhaust fan belt has failed again? No. The temperature differentials are not significant enough to use as a trustworthy trigger point. This is also becoming moot as one of the building projects is to thermostatically control the exhaust fan so we can start retaining the heat. Winter is coming.
In conclusion, the thermistors and the Picos were the only two things that performed as expected. The thermistors are easy to use and were solid performers. A thermistor and resistor should be in every starter kit, just like a diode and resistor. I will incorporate these thermistors into the temperature control that we want to add to the Variable Frequency Drive on the exhaust fan for my building. I have numerous incomplete ideas on how to solve our cold weather problems with emission compliant engines. It's an exciting time within my brain. These ideas may never come to production... but (again)... winter is coming. The enemy is at the gate. I may not... but I might.
p.s. I've included the Arduino code for one of the remote sensors and the receiver. The remote sensors node were the same code with different node numbers and the connected thermistor resistance and equivalent series resistance added. The only thing I forgot was that I changed the delay on transmit to 5 minutes instead of the 10 seconds in the example. The receiver code has had all of the data logging stuff purged. The receiver code also intentionally doesn't display a node until it successfully reports in. The counter is used to verify that unique nodes are reporting in and not just the same one.
Transmitter
/** * TRANSMITTER NODE * Based on helloworld_tx by 2012 James Coliz, Jr. <maniacbug@ymail.com> Updated 2014 by TMRh20 * Thermistor code from 2011 Limor Fried/ladyada for Adafruit Industries * * Transmits every interval (ms) and sends a payload to the receiver */ #include <SPI.h> #include <RF24.h> #include <RF24Network.h> RF24 radio(7, 8); // nRF24L01(+) radio attached using Getting Started board RF24Network network(radio); // Network uses that radio const uint16_t this_node = 04; // Address of our node in Octal format const uint16_t baseNode = 00; // Address of the other node in Octal format const unsigned long interval = 10000; // How often (in ms) to send readings to the base unit unsigned long last_sent; // When did we last send? unsigned long packets_sent; // How many have we sent already float reading; // Stores analog temperature reading. needs to be float for steinhart calculations #define THERMISTORNOMINAL 30000 // resistance at 25 degrees C #define TEMPERATURENOMINAL 20 // temp. for nominal resistance (almost always 25 C) #define BCOEFFICIENT 3982 // thermistor Beta #define SERIESRESISTOR 30000 // the value of the 'other' resistor + 15kohm as "calibration" struct payload_t { // Structure of our payload unsigned long steinhart; unsigned long counter; }; void setup(void) { Serial.begin(9600); /* while (!Serial) { // some boards need this because of native USB capability } */ Serial.println(F("RF24Network thermistor_tx")); if (!radio.begin()) { Serial.println(F("Radio hardware not responding!")); while (1) { // hold in infinite loop } } radio.setChannel(90); network.begin(/*node address*/ this_node); } void loop() { network.update(); // Check the network regularly unsigned long now = millis(); // If it's time to send a message, send it! if (now - last_sent >= interval) { last_sent = now; // Get temperature reading = 0; // start clean for (int i = 10; i > 0; i--) { reading = (reading + analogRead(A0)); } reading = (reading/10); // get average reading // convert the value to resistance reading = (SERIESRESISTOR*(1023-reading)/reading); Serial.print("Thermistor resistance "); Serial.println(reading); float steinhart; steinhart = (reading)/THERMISTORNOMINAL; // (R/Ro) steinhart = log(steinhart); // ln(R/Ro) steinhart = steinhart/BCOEFFICIENT; // 1/B * ln(R/Ro) steinhart += (1.0/(TEMPERATURENOMINAL + 273.15)); // + (1/To) steinhart = 1.0/steinhart; // Invert steinhart -= 273.15; // convert absolute temp to C Serial.print("Temperature "); Serial.print(steinhart); Serial.println(" *C"); steinhart = (steinhart*9/5 + 32); Serial.println(steinhart); Serial.print(F("Sending... ")); payload_t payload = { steinhart, packets_sent++ }; RF24NetworkHeader header(/*to node*/ baseNode); bool ok = network.write(header, &payload, sizeof(payload)); Serial.println(ok ? F("ok.") : F("failed.")); } }
Receiver
/* based on RF24Network/examples/helloworld_rx * */ #include <SPI.h> #include <RF24.h> #include <RF24Network.h> #include <Wire.h> // Needed for LCD display #include <LiquidCrystal_I2C.h> // Needed for LCD display RF24 radio(7, 8); // nRF24L01(+) radio attached using Getting Started board RF24Network network(radio); // Network uses that radio const uint16_t baseNode = 00; // Address of our node in Octal format (04, 031, etc) // const uint16_t node01 = 01; // Address of the other node in Octal format - didn't seem to be used at all // const uint16_t node02 = 02; // const uint16_t node03 = 03; // const uint16_t node04 = 04; // const uint16_t node05 = 05; struct payload_t { // Structure of our payload unsigned long steinhart; unsigned long counter; }; // LiquidCrystal_I2C lcd(0x27,16,2); // Set LCD address to 0x27. Set LCD for 16 chars by 2 line display. LiquidCrystal_I2C lcd(0x27,20,4); // Set LCD address to 0x27. Set LCD for 20 chars by 4 line display. // SETUP ******************************************************** void setup () { Serial.begin(9600); /* while (!Serial) { // some boards need this because of native USB capability - none of mine needed it } */ Serial.println(F("RF24Network_rx setup running")); if (!radio.begin()) { Serial.println(F("Radio hardware not responding!")); while (1) { // hold in infinite loop } } radio.setChannel(90); network.begin(/*node address*/ baseNode); // Initialize LCD lcd.init(); lcd.backlight(); lcd.clear(); } // LOOP ************************************************************* void loop () { network.update(); // Check the network regularly while (network.available()) { // Is there anything ready for us? RF24NetworkHeader header; // If so, grab it and print it out payload_t payload; network.read(header, &payload, sizeof(payload)); int node = (header.from_node); Serial.println(node); Serial.print(F("Rcv pkt: reading=")); Serial.print(payload.steinhart); Serial.print(F(", counter=")); Serial.println(payload.counter); lcd.setCursor(0,0); // Position cursor lcd.print(F("Temp Mon ")); // Start display of Line #1 switch (node) { case 1: // receive transmission from temperature node 01 lcd.setCursor(10,0); lcd.print("T1="); break; case 2: // receive transmission from temperature node 02 lcd.setCursor(0,1); lcd.print("T2="); break; case 3: // receive transmission from temperature node 03 lcd.setCursor(10,1); lcd.print("T3="); break; case 4: // receive transmission from temperature node 04 lcd.setCursor(0,2); lcd.print("T5="); break; case 5: // receive transmission from temperature node 05 lcd.setCursor(10,2); lcd.print("T6="); break; } lcd.print(payload.steinhart); // prints temperature corresponding with the case positioning above lcd.setCursor(0,3); lcd.print(F("counter=")); // print counter so we know that we're getting different readings lcd.print(payload.counter); // Add value delay(500); // Make display readable by humans } // End "while network" section *********************************** } // end loop ****************************************