//////////////////////////////////////////////////// // LCD multi-thermometer based on an Arduino Nano // //////////////////////////////////////////////////// // Measures the temperature using several one-wire sensors and displays // the results on a 4x20 alphanumeric LCD. // // Written in 2013 by Per Magnusson, Axotron, http://axotron.se/index_en.php // v 1.3 // // Part of the code was copied from: // https://github.com/pbrook/arduino-onewire/blob/master/examples/DS18x20_Temperature/DS18x20_Temperature.pde // The OneWire library was downloaded from: // http://www.pjrc.com/teensy/td_libs_OneWire.html // Download the OneWire lib and place its OneWire folder in // C:\Program Files\Arduino\libraries // Then restart the Arduino development environment. // // (c) 2013, Per Magnusson, Axotron // // License: // I make this code available in the hope that it might be educational and/or // inspirational. Feel free to use it, change it, include it in your own commercial // or non-commercial projects as you see fit. // I give no guarantee that it will be good for anything. // Below is some legalese I copied from a version of the BSD license: // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include // Specification of hardware connection: LiquidCrystal lcd(2, 3, 4, 5, 6, 7); OneWire ds(8); // on pin 8 (a 4.7K pull-up resistor is necessary) // Constants const byte N_SENSORS = 6; // Number of sensors const byte allow_serial = 0; // Set to 1 to enable serial port debug output const char title[] = " Termometer v 1.3 "; // Sensor addresses // To figure out the address of new sensors: set allow_serial to 1 and check with a // serial console what addresses the new sensors have const byte sensor_addr[N_SENSORS][8] = { {0x28, 0x92, 0xFD, 0xE2, 0x03, 0x00, 0x00, 0x21}, // ok {0x28, 0x01, 0x0c, 0xE3, 0x03, 0x00, 0x00, 0x2D}, // ok {0x28, 0xC5, 0x55, 0x95, 0x04, 0x00, 0x00, 0xD3}, // ok {0x28, 0x4E, 0x89, 0x95, 0x04, 0x00, 0x00, 0xBE}, // ok {0x28, 0x91, 0xA9, 0x95, 0x04, 0x00, 0x00, 0xA6}, // ok {0x28, 0xA9, 0xB1, 0x95, 0x04, 0x00, 0x00, 0xA8} // ok }; // Test position on the LCD, column - row const byte text_pos[N_SENSORS][2] = { {0, 1}, {10, 1}, {0, 2}, {10, 2}, {0, 3}, {10, 3}}; // Labels for the different sensor positions const char *labels[] = {"NG", "SG", "FX", "EX", "FL", "AL"}; // Norra gaveln, södra gaveln, ... const char *labels_long[] = {"Norra gaveln", "Sodra gaveln", "Tilluft fore VVX", "Tilluft efter VVX", "Franluft", "Avluft"}; // Counters for easter eggs byte brr_cnt = 15; byte puh_cnt = 15; byte sense_idx = 0; // Index to the sensor we would like to talk to byte miss_cnt = 0; // Number of times we have failed to find the desired sensor void setup(void) { lcd.begin(20, 4); lcd.setCursor(0,0); lcd.write(title); delay(3000); // To allow programmer to program a new sketch. // This is an attempt at a workaround for the: // 'avrdude: usbdev_open(): did not find any USB device "usb"' bug // But this workaround is probably ineffective... // It seems quite random when the bug hits. if(allow_serial) { Serial.begin(9600); } } void loop(void) { byte ii; byte present = 0; byte type_s; byte data[12]; byte addr[8]; byte crc_calc; double celsius; char sbuf[20]; if(sense_idx >= N_SENSORS) { // Wrap around if sense_idx is too high sense_idx = 0; } // Look for next 1-wire device if ( !ds.search(addr)) { // No more addresses if(allow_serial) { Serial.println("No more addresses."); Serial.println(); } ds.reset_search(); delay(250); return; } // Are we talking to the one we want to talk to? if(!addr_comp(sensor_addr[sense_idx], addr)) { // We are not talking to the sensor we would like to talk to, try the next one miss_cnt++; if(miss_cnt > N_SENSORS) { // The desired sensor does not seem to reply! if(allow_serial) { Serial.print("Cannot find sensor "); Serial.println(labels[sense_idx]); } // Show error on LCD lcd.setCursor(text_pos[sense_idx][0], text_pos[sense_idx][1]); lcd.write(labels[sense_idx]); lcd.write(":"); lcd.write(" Err! "); // Go to next sensor index miss_cnt = 0; sense_idx++; } // Try to search again return; } // Found the desired sensor, its index is in sense_idx miss_cnt = 0; // Write label explanation lcd.setCursor(0,0); lcd.write(" "); // Clear top line of LCD lcd.setCursor(0,0); lcd.write(labels[sense_idx]); lcd.write(" "); lcd.write(labels_long[sense_idx]); if(allow_serial) { Serial.print("ROM ="); for(ii = 0; ii < 8; ii++) { Serial.write(' '); Serial.print(addr[ii], HEX); } } crc_calc = OneWire::crc8(addr, 7); if (crc_calc != addr[7]) { if(allow_serial) { Serial.println(); Serial.print("CRC is not valid!"); Serial.println(crc_calc, HEX); } return; } // the first ROM byte indicates which chip switch (addr[0]) { case 0x10: if(allow_serial) { Serial.println(" Chip = DS18S20"); // or old DS1820 } type_s = 1; break; case 0x28: if(allow_serial) { Serial.println(" Chip = DS18B20"); } type_s = 0; break; case 0x22: if(allow_serial) { Serial.println(" Chip = DS1822"); } type_s = 0; break; default: if(allow_serial) { Serial.println("Device is not a DS18x20 family device."); } return; } ds.reset(); ds.select(addr); ds.write(0x44, 1); // start conversion, with parasite power on at the end // Turn off degree sign to show that a conversion is in progress lcd.setCursor(text_pos[sense_idx][0]+8, text_pos[sense_idx][1]); lcd.write(" "); delay(1000); // maybe 750ms is enough, maybe not // we might do a ds.depower() here, but the reset will take care of it. present = ds.reset(); ds.select(addr); ds.write(0xBE); // Read Scratchpad if(allow_serial) { Serial.print(" Data = "); Serial.print(present, HEX); Serial.print(" "); } for (ii = 0; ii < 9; ii++) { // we need 9 bytes data[ii] = ds.read(); if(allow_serial) { Serial.print(data[ii], HEX); Serial.print(" "); } } if(allow_serial) { Serial.print(" CRC="); Serial.print(OneWire::crc8(data, 8), HEX); Serial.println(); } // Convert the data to actual temperature // because the result is a 16 bit signed integer, it should // be stored to an "int16_t" type, which is always 16 bits // even when compiled on a 32 bit processor. int16_t raw = (data[1] << 8) | data[0]; if (type_s) { raw = raw << 3; // 9 bit resolution default if (data[7] == 0x10) { // "count remain" gives full 12 bit resolution raw = (raw & 0xFFF0) + 12 - data[6]; } } else { byte cfg = (data[4] & 0x60); // at lower res, the low bits are undefined, so let's zero them if (cfg == 0x00) raw = raw & ~7; // 9 bit resolution, 93.75 ms else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms //// default is 12 bit resolution, 750 ms conversion time } celsius = (float)raw / 16.0; if(allow_serial) { Serial.print(" Temperature = "); Serial.print(celsius); Serial.println(" C"); } dtostrf(celsius, 5, 1, sbuf); if(celsius < -10.0) { // Brr! easter egg shown occasionally at cold temperatures brr_cnt++; if(brr_cnt > 17) { brr_cnt = 0; sbuf[0] = ' '; sbuf[1] = 'B'; sbuf[2] = 'r'; sbuf[3] = 'r'; sbuf[4] = '!'; sbuf[5] = ' '; sbuf[6] = '\0'; } } else if(celsius > 27.0) { // Puh! easter egg shown occasionally at hot temperatures puh_cnt++; if(puh_cnt > 17) { puh_cnt = 0; sbuf[0] = ' '; sbuf[1] = 'P'; sbuf[2] = 'u'; sbuf[3] = 'h'; sbuf[4] = '!'; sbuf[5] = ' '; sbuf[6] = '\0'; } } //lcd.setCursor(0,0); // Not any more since we started writing the explanations //lcd.write(title); lcd.setCursor(text_pos[sense_idx][0], text_pos[sense_idx][1]); lcd.write(labels[sense_idx]); lcd.write(":"); lcd.write(sbuf); if((sbuf[1] != 'B') && (sbuf[1] != 'P')){ // Show degree sign when not showing easter eggs Brr! or Puh! lcd.write(0xDF); // degree sign lcd.write(' '); // space in case something else was written here } // Update to next sensor sense_idx++; } byte addr_comp(const byte *a1, const byte *a2) { // Compare two OneWire addresses and return true iff they are equal byte ii; byte res = 1; for(ii=0; ii<8; ii++) { if(a1[ii] != a2[ii]) { res = 0; break; } } return res; }