Temperature logger to SD Card

Hi Folks,
I have modified some code that reads six 1 wire temperature devices so that it stores the data to a SD card.
The original code works perfectly and dumps the values to the serial port and having let it run for many minutes does not stop or halt.
I modified the code to store the data to a file on the SD card.
As far as I can tell the libraries are all okay etc as the code works fine for a few minutes logging to the SD card okay.

After maybe 3 minutes the code halts and no further information is sent to the serial port (screen) or SD card.

The code I have added to save data to the SD card has 4 asterisks (****) after the comments identifier.

Any help you can offer would be well appreciated as I am a bit of a new starter at this!

#include <SD.h>
#include <SPI.h>

#include <OneWire.h>

// OneWire DS18S20, DS18B20, DS1822 Temperature Example
//
// http://www.pjrc.com/teensy/td_libs_OneWire.html
//
// The DallasTemperature library can do all this work for you!
// http://milesburton.com/Dallas_Temperature_Control_Library

OneWire  ds(2);  // on pin 10 (a 4.7K resistor is necessary)

int chipSelect=4; //****set chipSelect to 4

File mySensorData; //****variable for date file


void setup(void) {
  Serial.begin(9600);
  
 pinMode(10,OUTPUT); // ****pinMode 10
 
 
}

void loop(void) {
  byte i;
  byte present = 0;
  byte type_s;
  byte data[12];
  byte addr[8];
  float celsius, fahrenheit;
  
 SD.begin(chipSelect);  //****initialise SD card with chip select 
 mySensorData= SD.open("TempData.txt", FILE_WRITE); //****Open file on SD card for writing to

if (mySensorData) { 

  if ( !ds.search(addr)) {
    Serial.println("No more addresses.");
    Serial.println();
    ds.reset_search();
    delay(250);
    return;
  }

  Serial.print("ROM =");
  for( i = 0; i < 8; i++) {
    Serial.write(' ');  //puts spaces between each hex value
    Serial.print(addr[i], HEX);
    //mySensorData.write(' ');  //****puts spaces between each hex value
    mySensorData.print(addr[i], HEX);   //****save to SD card ROM address
       
  }

  if (OneWire::crc8(addr, 7) != addr[7]) {
      Serial.println("CRC is not valid!");
      return;
  }
  Serial.println();

  // the first ROM byte indicates which chip
  switch (addr[0]) {
    case 0x10:
      Serial.println("  Chip = DS18S20");  // or old DS1820
      type_s = 1;
      break;
    case 0x28:
      Serial.println("  Chip = DS18B20");
      type_s = 0;
      break;
    case 0x22:
      Serial.println("  Chip = DS1822");
      type_s = 0;
      break;
    default:
      Serial.println("Device is not a DS18x20 family device.");
      return;
  }

  ds.reset();
  ds.select(addr);
  ds.write(0x44);        // start conversion, use ds.write(0x44,1) with parasite power on at the end

  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

  Serial.print("  Data = ");
  Serial.print(present, HEX);
  Serial.print(" ");
  for ( i = 0; i < 9; i++) {           // we need 9 bytes
    data[i] = ds.read();
    Serial.print(data[i], HEX);
    Serial.print(" ");
  }
  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;
  fahrenheit = celsius * 1.8 + 32.0;
  Serial.print("  Temperature = ");
  Serial.print(celsius);
  Serial.println(" Celsius, ");
  //Serial.print(fahrenheit);
  //Serial.println(" Fahrenheit");
  
  mySensorData.print(", ");  //****print comma in string to make CSV file
  mySensorData.println(celsius);   //****save to SD card Celsius
  mySensorData.close();  //****close file
  
}

}

What do you see on the SD card after running it? Is the file there? What does it contain?

Hi, thanks for the reply.
When I view the SD card the file is there, seems intact, is readable and contains the data I expect it to contain up until the point the process stopped. The file name is also what is stated in the code so all seems correct there as well.

I am wondering if I am running too much code between when the sd card file opens and then closes.
Would this be causing some form of issue?

As I am building up my coding knowledge I am struggling to see how I can still have an undefined number of 1 wire sensors (which this code handles) and also then write the values to the sd, but make the code smarter.

I also seem to think that I ought to be writing the data to a string value then just dump it to file at the latter stage of the loop, but again still building up my coding and am struggling to see how I can do this without it all going toes up!

Cheers for you help,

Isn't this:

    if (OneWire::crc8(addr, 7) != addr[7]) {

supposed to be:

    if (ds.crc8(addr, 7) != addr[7]) {

?

Since you are opening and closing the SD in each loop cycle, which is fair enough, it would be better if all the SD commands were brought together. As it is, if you aren’t getting any temperature data on the card, I think it is because you aren’t sending any. You may be getting sensor addresses, but I don’t know why you would want to do that.

The code is obsolete junk. The only useful line in it is

// The DallasTemperature library can do all this work for you!

which isn’t even code, and makes you wonder why anybody would ever publish such rubbish in the first place. It isn’t as if they don’t know it’s rubbish.

You will find this more useful, not to mention more compact.

http://www.hacktronics.com/Tutorials/arduino-1-wire-tutorial.html

Once you have that together, you just need something like

           myFile = SD.open(filename, FILE_WRITE);//<<<<<<<<<<<<< OPEN
  myFile.print(hour);
  myFile.print(":");
  myFile.print(minute);
  myFile.print(":");
  myFile.print(second);
  myFile.print(",");

  myFile.print(InTemp);
  myFile.print(",");
  myFile.print(OutTemp);
  myFile.print(",");
  myFile.println(DrainTemp);
       myFile.close();//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>CLOSE

At the end of the loop.

I can’t tell why it dies after three minutes. I think the necessary delays are accounted for. Perhaps it just gives up in despair.

Hi Aarg and Nick,
Thanks for the replies.
The crc gets the crc value which for me is irrelevant as I do not need it.
Changing it to you line of code Aarg may or may not make a difference.

I did start out by doing all my Sd card related outputting in one section towards the end but it would not compile, hence thats why I placed it at the various places it is at currently.

I do need to know the device id as I am hopefully going to bring the values back into a database at some point. What order the values come back in from the 1 wire service will matter not if I know the address. SQL or similar will be able to handle the rest for me. I think!!!

The code may be outdated, as I said I am fresh at this and learning more each time I dabble. I do have previous experience at a small level regarding Vba and HTML but nothing that's gunna set the world on fire so I would not be able to spot the age issue that you have spotted with the example code that I used to start my project with.

Thanks for your help guys.

Still struggling on this end bit by bit.

Hi Nick,
Tried your attached link.
The sketch via the link uses code that requires you to hard code the sensor address.

While it may be more modern I still could do with a way of establishing the sensor address and resultant temperature value from each sensor.

As my code is outdated then what else should you be able to suggest to grab id and sensor value?

Could the best way forward be to write to SD at the end of the loop but assemble a string as the code is processed and then open file - write the string to it -then close the file???

Cheers for the help.

There is no need for any expertise with Vba or HTML and you only need the faintest wiff of expertise with Arduino to get suus about that stuff. The line

// The DallasTemperature library can do all this work for you!

tells you all you need to know. If the library does it all for you, why are you bothering with this junk?

I suspect you did not read the tutorial. Included therein is a separate programme that reads and reports the address. You use these addresses in the data-logger but use them to assign names that everybody can understand, like InTemp, OutTemp, and DrainTemp, as shown in the example.

There is no need, or point, in using strings, and this forum is littered with the bodies of those who have come to grief trying to do so. You simply receive the floats from the sensors and pass them along to LCD, serial, or SD, and all in much the same manner. Along with the extra effort and risk with strings, I understand you also suffer a significant speed hit which, while probably not important now, may be something you regret later.

The only time you actually need to use strings is when a programme you are feeding data to demands it. The only case in point I know of is a bluetooth graphics terminal programme I use.

It seems to me, you should collect your device ID's once, in setup( ), and save them in an array to be used each time in loop( ).

Or even, collect them with another sketch and then put the devices manually into your logging code, which is what I did.

Hi all,
So I have made some progress in using the libraries to do the work.
But…
I have the following code below that I don’t fully understand.
The printAddress(tempDeviceAddress); and printTemperature(tempDeviceAddress) are variables that successfully display the device address and also temperature.
Now all I want to do is to drop these values into a string.
I have tried all sorts but to no success.

Any ideas folks would be most welcome.

Thanks again.

// Loop through each device, print out temperature data
  for(int i=0;i<numberOfDevices; i++)
    {
    // Search the wire for address
    if(sensors.getAddress(tempDeviceAddress, i))
	{
                                  
                printAddress(tempDeviceAddress);  // ////prints device address
                              
		
		// It responds almost immediately. Let's print out the data
		printTemperature(tempDeviceAddress); // Use a simple function to print out the data
            

	} 
		
  }

Twosides:
Now all I want to do is to drop these values into a string.

Why?

Hi Nick
Being fairly new to Arduino coding I look for help from others code and try and see if I can understand it.
Looking at the code in the Dallas and One wire examples sketches I found one I can sort of figure out.
The only bit I cannot get my mind around is this section.
The reason I want to write it to a string is to do all the Sd card functions in one hit due to my previous code not working after a few minutes.
As I do not know why that worked but then failed I am trying to keep the Sd card stuff simple and condensed I case it's that that may be giving me trouble.
As newbie I'm struggling especially not knowing why my previous adaptation fails or gave up!

All I want to do is write to a txt or csv file on the sd card "chip address,temp" per sweep or the 1 wire system.

Cheers

Twosides:
All I want to do is write to a txt or csv file on the sd card "chip address,temp" per sweep or the 1 wire system.

You don't need strings to do that. Reading reply #4 again might help. Recording a proper name for the sensor makes more sense than the chip address and there is really no need to be the first person in the universe not to do that. It is likely that you won't need the name anyway. Further, since you are recording events, you might find subsequent knowledge of when the events happened a really good idea, hence the time stamping as shown, using an on-board RTC.

This clock can also provide a convenient way to name files by date etc.

Note that you probably don't need the clock if you are recording data externally. Most PC terminal programmes can timestamp using the PC's clock.

Hi Nick, thanks for reply.
I do get what you are saying. Ultimately I will add a RTC but as a learner I want to solve one thing at a time. The RTC will come next.
I actually want to be able to use many 1 wire devices to monitor in excess of 20 or so temperatures on a building project. I can name them yes, it is probably the best ultimately I agree. Maybe I will have to do this anyway to sove the issue although I feel like giving up at present. Been working on this for a good number of days on and off thinking I could solve the puzzle.

What I had hoped for ultimately is to use the sensor address, the time stamp and temperature to carry over into a database. The database would match the device address against a human identifiable name or label. The data would then be graphed etc for display purposes.

Maybe I just quit now but feel I am close to the solution somehow.

Thanks.

That sounds exactly what I am doing except I have only three, soon four, DS18B20, plus a water turbine.

I never send the sensor names to SD file or to Excel. The sensors are known by the order in which their data is received. The same applies applies when data is retrieved from SD for subsequent insertion into Excel. The names are really just for local reference i.e. within the programme.

It is possible to send the data direct to Excel, thereby displaying graphs in real time. Similar to a standard terminal, Excel can insert timestamping by itself. In short, an RTC is only needed if you are doing on-board recording.

I now see that junk code you have is an example from the one-wire library. That crowd ought to be bloody ashamed of themselves. I guess I was lucky in that I came at all this via the Hacktronics tutorial, and I never knew that code was there.

The following code is not exactly the same as that from Hacktronics, but I can’t remember what the difference is. I think it is just procedural in order to expand it more easily.

/* Basic 2xDS18B20 code for serial monitor, bluetooth, Excel or w.h.y.
Derived from Hacktronics. USE THEIR ADDRESS SNIFFER and substitute your 
numbers. Use Hacktronics connections diagram. 
http://www.hacktronics.com/Tutorials/arduino-1-wire-tutorial.html
Stay away from using parasite power
-127C means bad connection
85 means you haven't gotten a read yet, probably just the 
wrong order of commands
*/

#include <OneWire.h>
#include <DallasTemperature.h>
#include "Wire.h"                
#include <SD.h>
#include <SPI.h>                 

// Data wire is plugged into pin 3 on the Arduino
#define ONE_WIRE_BUS 3

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);
  
byte Thermo1[8] = {0x28, 0x39, 0xFD, 0x50, 0x04, 0x00, 0x00, 0X69};
byte Thermo2[8] = {0x28, 0x09, 0xA9, 0xC0, 0x03, 0x00, 0x00, 0x95};
float tempC,Temp1,Temp2;  

char filename[] = "Test.CSV";
File myFile;

void setup(){
  Serial.begin(9600);
  sensors.begin();
  delay(500);//Wait for newly restarted system to stabilize
  
   // make sure that the default chip select pin is set to
  // output, even if you don't use it:
  //pinMode(10, OUTPUT);// Uno
  pinMode(53, OUTPUT);//MEGA
    if (!SD.begin(chipSelect)) 
  {
    Serial.println("Card failed");
    // don't do anything more:
    return;
  }
    Serial.println("CARD OK");
}

void loop() {
 sensors.requestTemperatures();  // call readings from the addresses
  Temp1 = sensorValue(Thermo1);
  Temp2 = sensorValue(Thermo2);  

Serial.print("      Temp1 = ");
Serial.print(Temp1);
Serial.print("      Temp2 = "); 
Serial.println(Temp2);

  myFile = SD.open(filename, FILE_WRITE);//<<<<<<<<<<<<< OPEN
  myFile.print(Temp1);
  myFile.print(",");
  myFile.println(Temp2);
       myFile.close();//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>CLOSE
       
delay(1000);
}

//sensorValue function
float sensorValue (byte deviceAddress[])
{
  tempC = sensors.getTempC (deviceAddress);
  return tempC;
}
So I have made some progress in using the libraries to do the work.

Can you please post your current code and a concise description of what you would like it to do, and a problem statement of what it is not doing.

I can name them yes, it is probably the best ultimately I agree

Yes, this is the best way to go. You will have declarations like this

byte indoorSensor[] = {
 0x28, 0x6A, 0x51, 0x5D, 0x05, 0x00, 0x00, 0x3A};
byte outdoorSensor[] = {
  0x28, 0xF1, 0x1E, 0x5D, 0x05, 0x00, 0x00, 0xF5};

And get readings into variables which you can log

 sensors.requestTemperatures();
indoorSensorDegreesF = sensors.getTempF(indoorSensor);
outdoorSensorDegreesF = sensors.getTempF(outdoorSensor);

Reading by direct address is 2x faster than reading by index.

Hi Cattledog,
My code is below. If you were to run it you will see it works.

I have added a string line at line 86. This works so far, all i need to do is drop in a value for the sensor address where i input the “zzz”.
I appreciate i may have to amend the string later on in the code.

Would much appreciate any thoughts.

I will then drop out the string to a SD card - hopefully.

Cheers,

#include <OneWire.h>
#include <DallasTemperature.h>
#include <SD.h>
#include <SPI.h>

// Data wire is plugged into port 2 on the Arduino
#define ONE_WIRE_BUS 2
#define TEMPERATURE_PRECISION 10

// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);

int numberOfDevices; // Number of temperature devices found
String dataString = "";
DeviceAddress tempDeviceAddress; // We'll use this variable to store a found device address

const int chipSelect = 4;

void setup(void)
{
  // start serial port
  Serial.begin(9600);
  Serial.println("Dallas Temperature IC Control Library Demo");


  Serial.println("Initializing SD card...");   //dc added
  // make sure that the default chip select pin is set to
  // output, even if you don't use it:
  pinMode(10, OUTPUT);  //needed for SD card
    
  
  // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    // don't do anything more:
    return;
  }


  // Start up the library
  sensors.begin();  
  
  
  // Grab a count of devices on the wire
  numberOfDevices = sensors.getDeviceCount();

  
  // Loop through each device, print out address
  for(int i=0;i<numberOfDevices; i++)
  {
    // Search the wire for address
    if(sensors.getAddress(tempDeviceAddress, i))
	{
		Serial.print("Found device ");
		Serial.print(i, DEC);
		Serial.print(" with address: ");
		printAddress(tempDeviceAddress);
		Serial.println();

		
		// set the resolution to 9 bit (Each Dallas/Maxim device is capable of several different resolutions)
		sensors.setResolution(tempDeviceAddress, TEMPERATURE_PRECISION);
		

	}else{
		Serial.print("Found ghost device at ");
		Serial.print(i, DEC);
		Serial.print(" but could not detect address. Check power and cabling");
	}
  }

}

// function to print the temperature for a device
void printTemperature(DeviceAddress deviceAddress)
{
 
  // method 2 - faster
  float tempC = sensors.getTempC(deviceAddress);
  Serial.print(" Temp C: ");
  Serial.println(tempC);
  
  dataString = String(tempC);
  dataString += ",";
  dataString += "zzz";  //need some code here to drop in the sensor ID
  /////****above string code needs work*****
  
  
  Serial.println(dataString);  //test the string output by printing it to screen
  //Serial.print(" Temp F: ");
  //Serial.println(DallasTemperature::toFahrenheit(tempC)); // Converts tempC to Fahrenheit
}

void loop(void)
{ 
  // call sensors.requestTemperatures() to issue a global temperature 
  // request to all devices on the bus
  Serial.print("Requesting temperatures...");
  sensors.requestTemperatures(); // Send the command to get temperatures
  Serial.println("DONE");
  Serial.println();
  delay(2000);
  
    
  // Loop through each device, print out temperature data
  for(int i=0;i<numberOfDevices; i++)
    {
    // Search the wire for address
    if(sensors.getAddress(tempDeviceAddress, i))
	{
                                  
                printAddress(tempDeviceAddress);  // ////prints device address
                              
		
		// It responds almost immediately. Let's print out the data
		printTemperature(tempDeviceAddress); // Use a simple function to print out the data
            

	} 
		
  }
}

// function to print a device address
void printAddress(DeviceAddress deviceAddress)
{
  for (uint8_t i = 0; i < 8; i++)
  {
    if (deviceAddress[i] < 16) Serial.print("0");
    Serial.print(deviceAddress[i], HEX);
  }
}

cattledog:
Reading by direct address is 2x faster than reading by index.

That's interesting. I always thought reading by index was a dumb idea but I never knew it was slower. I guess that is because the order has to be sorted out every trip round the loop.