In the previous post, I explained how to install MySQL and how to create a database for use with Arduino projects. Now it’s time to go further and to explain how to create an Arduino temperature and humidity data logger.
Part 1 – the hardware
The project uses the following parts:
– one Seeeduino Mega (or any other Arduino Mega board) – this is mandatory as the code is quite large
– one Ethernet Shield
– one SEN-08257 temperature / humidity sensor from Sparkfun (based on SHT15x)
– one BOB-12708 real time module from Sparkfun (based on DS1307 RTC)
– one breadboard
– some connecting wires

In this schematic the RTC clock uses the I2C pins SDA and SCL, which in the case of Seeeduino MEga are on a separate small connector. For the SHT15 sensor, although is I2C compatible, I preferred to use the pins 7 and 8. The Ethernet shield comes on top of the Seeeduino Mega. The SPI data lines are from the ICSP connector, and the CS signals are on pins 4 and 10. Some pictures of the completed project are presented below. Note that the Seeeduino Mega has the holes in a different placement than in the classical Arduino Uno. To install it on the Arduino holder (DEV-11235) I had to remove the original fixing pins, then fix the Seeeduino board using two layers of thick double sided tape.
Part 2 – the software
In this project we first set the RTC timer using NTP, then we monitor the RTC, and every 10 minutes we log the time, temperature and humidity into the ardulogger database. Further, we will use DBPlot to create graphs of temperature and humidity.
For this project some prerequisites are needed:
First, we need the updated time library. Just download it and extract the files in the library folder of Arduino IDE.
Next, we need the SHT1x library from https://github.com/practicalarduino/SHT1x. After extracting the files into the library folder of Arduino IDE we have to rename it from SHT1x-master to SHT1xmaster, otherwise, we will get errors when compiling.
Finally, we have to install the MySQL connector libraries, which can be downloaded from https://launchpad.net/mysql-arduino. The version I used is mysql_connector_arduino-1.0.2b.zip.
To work with MySQL a small modification is needed: in mysql.h you need to uncomment the line:
#define WITH_SELECT // Uncomment this for use without SELECT capability
Try to compile the example files. Apply any diff files in the sha1 library if needed (I highly recommend http://winmerge.org/ to do this). If something goes wrong, you may take a look on Chuck’s blog, which gives a very good introduction to the MySQL connector. All we care about in this step is to be sure that the examples compile with no errors.
The code is as follows:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/******************************************************************* | |
____ __ ____ ___ ____ ____ __ __ _ ____ __ | |
( __)( ) ( __)/ __)(_ _)( _ \ / \ ( ( \(__ ) / _\ | |
) _) / (_/\ ) _)( (__ )( ) /( O )/ / / _/ / \ | |
(____)\____/(____)\___) (__) (__\_) \__/ \_)__)(____)\_/\_/ | |
Project name: Arduino: data logger with MySQL | |
Project page: https://electronza.com/arduino-data-logger-mysql/ | |
********************************************************************/ | |
#include <SPI.h> | |
#include <Ethernet.h> | |
#include <Dns.h> | |
#include <Time.h> | |
#include <Wire.h> | |
#include <DS1307RTC.h> | |
#include <SHT1x.h> | |
#include <sha1.h> | |
#include <mysql.h> | |
#include <string.h> | |
/* ******** Ethernet Card Settings ******** */ | |
// Set this to your Ethernet Card Mac Address | |
byte mac[] = {0x90, 0xA2, 0xDA, 0x0D, 0xFE, 0x43 }; | |
/* ******** NTP Server Settings ******** */ | |
/* us.pool.ntp.org NTP server | |
(Set to your time server of choice) */ | |
IPAddress timeServer; | |
/* Set this to the offset (in seconds) to your local time | |
This example is GMT + 2 */ | |
const long timeZoneOffset = 7200L; | |
/* Syncs to NTP server every 15 seconds for testing, | |
set to 1 hour or more to be reasonable */ | |
unsigned int ntpSyncTime = 15; | |
/* ALTER THESE VARIABLES AT YOUR OWN RISK */ | |
// local port to listen for UDP packets | |
unsigned int localPort = 8888; | |
// NTP time stamp is in the first 48 bytes of the message | |
const int NTP_PACKET_SIZE= 48; | |
// Buffer to hold incoming and outgoing packets | |
byte packetBuffer[NTP_PACKET_SIZE]; | |
// A UDP instance to let us send and receive packets over UDP | |
EthernetUDP Udp; | |
/* ********** RTC Time settings ********* */ | |
tmElements_t tm; | |
int minutes_now; | |
int minutes_next; | |
/* ******* Settings for the SHT1x sensor */ | |
#define dataPin 9 | |
#define clockPin 8 | |
SHT1x sht1x(dataPin, clockPin); | |
float temp_c; | |
float humidity; | |
/* ***** MySQl Server settings ********** */ | |
Connector my_conn; // The Connector/Arduino reference | |
IPAddress server_addr(192, 168, 1, 133); // IP address of MySQL server | |
char user[] = "arduino"; | |
char password[] = "arduino"; | |
// maximum tries to connect | |
int num_fails; | |
#define MAX_FAILED_CONNECTS 5 | |
// We define some strings | |
char myquery[150]; // more than enough; stores command to print table content | |
char insquery[200]; // more than enough; stores command to insert new data | |
const char testselect[] ={"SELECT * FROM ardulogger.ardudata ORDER BY id DESC LIMIT %s"}; | |
// general format to insert data is: | |
//const char insertdata[] = "INSERT INTO ardulogger.ardudata (date,time,temp,humi) VALUES ('2014-11-11','11:45:11', 25.8 , 31.21)"; | |
//const char insertdata[] = {"INSERT INTO ardulogger.ardudata (date,time,temp,humi) VALUES ('%s-%s-%s','%s:%s:%s', %s , %s)"}; | |
const char insertdata[] = {"INSERT INTO ardulogger.ardudata (date,time,temp,humi) VALUES ('%s-%s-%s','%s:%s:%s', %s , %s)"}; | |
// buffer for number to character conversion | |
char yearbuf[5]; | |
char monthbuf[3]; | |
char daybuf[3]; | |
char hourbuf[3]; | |
char minutebuf[3]; | |
String temp_buf; | |
char secbuf[3]; | |
char tempbuf[6]; | |
char humibuf[5]; | |
int timebuf; | |
void setup() { | |
Serial.begin(9600); | |
// Ethernet shield and NTP setup | |
int i = 0; | |
int DHCP = 0; | |
DHCP = Ethernet.begin(mac); | |
//Try to get dhcp settings 30 times before giving up | |
while( DHCP == 0 && i < 30){ | |
delay(1000); | |
DHCP = Ethernet.begin(mac); | |
i++; | |
} | |
if(!DHCP){ | |
Serial.println("DHCP FAILED"); | |
for(;;); //Infinite loop because DHCP Failed | |
} | |
Serial.println("DHCP Success"); | |
//Just for testing we print the obtained IP address | |
Serial.print("My IP address: "); | |
for (byte thisByte = 0; thisByte < 4; thisByte++) { | |
// print the value of each byte of the IP address: | |
Serial.print(Ethernet.localIP()[thisByte], DEC); | |
Serial.print("."); | |
} | |
Serial.println(); | |
// We get the address of a NTP server from NTP pool | |
DNSClient dns; | |
dns.begin(Ethernet.dnsServerIP()); | |
dns.getHostByName("ro.pool.ntp.org",timeServer); | |
Serial.print("NTP IP from the pool: "); | |
Serial.println(timeServer); | |
// Now we get the time | |
Serial.println("trying to get the time"); | |
int trys=0; | |
while(!getTimeAndDate() && trys<10){ | |
trys++; | |
} | |
if(trys<10){ | |
Serial.println("Ntp server update success"); | |
} | |
else{ | |
Serial.println("Ntp server update failed"); | |
} | |
// Print the time | |
clockDisplay(); | |
// We have the right time, now we set the RTC clock | |
if (RTC.read(tm)) { | |
Serial.println("The DS1307 is running."); | |
Serial.println("Updating the RTC time."); | |
Serial.println(); | |
tm.Day = day(); | |
tm.Month = month(); | |
tm.Year = year()-1970; | |
tm.Hour = hour(); | |
tm.Minute = minute(); | |
tm.Second = second(); | |
RTC.write(tm); //update the time | |
} else if (RTC.chipPresent()) { | |
Serial.println("The DS1307 stopped."); | |
Serial.println("Setting the RTC time."); | |
Serial.println(); | |
tm.Day = day(); | |
tm.Month = month(); | |
tm.Year = year()-1970; | |
tm.Hour = hour(); | |
tm.Minute = minute(); | |
tm.Second = second(); | |
RTC.write(tm); //update the time | |
} | |
else { | |
Serial.println("DS1307 read error! Please check the circuitry."); | |
Serial.println(); | |
} | |
// Just in case we read the RTC time for debugging purposes | |
// Serial.println("Computing the next DB access time"); | |
RTC.read(tm); | |
minutes_now=tm.Minute; | |
// minutes_next=floor(tm.Minute/10)*10+10; | |
minutes_next=minutes_now+1; //debgging – write every minute to DB | |
if (minutes_next==60) | |
minutes_next=0; | |
Serial.print("Current minute: "); | |
Serial.print(minutes_now); | |
Serial.println(); | |
Serial.print("Minute for the next database write: "); | |
Serial.print(minutes_next); | |
Serial.println(); | |
} //end of setup | |
// functions for NTP server | |
// Do not alter this function, it is used by the system | |
int getTimeAndDate() { | |
int flag=0; | |
Udp.begin(localPort); | |
sendNTPpacket(timeServer); | |
delay(1000); | |
if (Udp.parsePacket()){ | |
Udp.read(packetBuffer,NTP_PACKET_SIZE); // read the packet into the buffer | |
unsigned long highWord, lowWord, epoch; | |
highWord = word(packetBuffer[40], packetBuffer[41]); | |
lowWord = word(packetBuffer[42], packetBuffer[43]); | |
epoch = highWord << 16 | lowWord; | |
epoch = epoch – 2208988800 + timeZoneOffset; | |
flag=1; | |
setTime(epoch); | |
//ntpLastUpdate = now(); not needed anymore as we update the time only once | |
} | |
return flag; | |
} | |
// Do not alter this function, it is used by the system | |
unsigned long sendNTPpacket(IPAddress& address) | |
{ | |
memset(packetBuffer, 0, NTP_PACKET_SIZE); | |
packetBuffer[0] = 0b11100011; | |
packetBuffer[1] = 0; | |
packetBuffer[2] = 6; | |
packetBuffer[3] = 0xEC; | |
packetBuffer[12] = 49; | |
packetBuffer[13] = 0x4E; | |
packetBuffer[14] = 49; | |
packetBuffer[15] = 52; | |
Udp.beginPacket(address, 123); | |
Udp.write(packetBuffer,NTP_PACKET_SIZE); | |
Udp.endPacket(); | |
} | |
// Clock display of the time and date (Basic) | |
void clockDisplay(){ | |
Serial.print(hour()); | |
printDigits(minute()); | |
printDigits(second()); | |
Serial.print(" "); | |
Serial.print(day()); | |
Serial.print(" "); | |
Serial.print(month()); | |
Serial.print(" "); | |
Serial.print(year()); | |
Serial.println(); | |
} | |
// Utility function for clock display: prints preceding colon and leading 0 | |
void printDigits(int digits){ | |
Serial.print(":"); | |
if(digits < 10) | |
Serial.print('0'); | |
Serial.print(digits); | |
} | |
// End of NTP functions | |
void print2digits(int number) { | |
if (number >= 0 && number < 10) { | |
Serial.write('0'); | |
} | |
Serial.print(number); | |
} | |
void loop(){ | |
RTC.read(tm); | |
minutes_now=tm.Minute; | |
if (minutes_now==minutes_next){ | |
//if (time_now.Minute==minutes_next){ | |
// compute the next time for write | |
//minutes_next=floor(tm.Minute/10)*10+10; // write every 10 minutes – normal mode | |
minutes_next=minutes_now+1; // write every minute – debug mode | |
if (minutes_next==60) | |
minutes_next=0; | |
temp_c = sht1x.readTemperatureC(); | |
humidity = sht1x.readHumidity(); | |
// we construct the INSERT code here | |
// first we initialize the buffers | |
temp_buf=String(tm.Year+1970); | |
temp_buf.trim(); | |
temp_buf.toCharArray(yearbuf,5); | |
temp_buf=String(tm.Month); | |
temp_buf.trim(); | |
temp_buf.toCharArray(monthbuf,3); | |
temp_buf=String(tm.Day); | |
temp_buf.trim(); | |
temp_buf.toCharArray(daybuf,3); | |
temp_buf=String(tm.Hour); | |
temp_buf.trim(); | |
temp_buf.toCharArray(hourbuf,3); | |
temp_buf=String(tm.Minute); | |
temp_buf.trim(); | |
temp_buf.toCharArray(minutebuf,3); | |
temp_buf=String(tm.Second); | |
temp_buf.trim(); | |
temp_buf.toCharArray(secbuf,3); | |
// float to char conversion | |
dtostrf(temp_c, 5 , 2, tempbuf); | |
dtostrf(humidity, 5, 2, humibuf); | |
// now we build the command for data insertion | |
sprintf(insquery,insertdata,yearbuf,monthbuf,daybuf,hourbuf,minutebuf,secbuf,tempbuf,humibuf); | |
//ready to work with the database | |
Serial.println("DB write: "); | |
// Here comes the MySQL data code | |
if (my_conn.mysql_connect(server_addr, 3306, user, password)) { | |
// check for connection | |
if (my_conn.is_connected()) { | |
Serial.println("Last 5 DB entries!"); | |
sprintf(myquery,testselect,"5"); | |
Serial.println(myquery); | |
my_conn.cmd_query(myquery); | |
delay(250); // small delay to wait for MySQl server to respond | |
my_conn.show_results(); | |
delay(1000); | |
Serial.println("Inserting new data!"); | |
Serial.println(insquery); | |
my_conn.cmd_query(insquery); | |
delay(1500); // small delay to wait for MySQl server to respond | |
// We verify the last entered data | |
Serial.println("New data entered!"); | |
sprintf(myquery,testselect,"1"); | |
Serial.println(myquery); | |
my_conn.cmd_query(myquery); | |
delay(250); // small delay to wait for MySQl server to respond | |
my_conn.show_results(); | |
num_fails = 0; | |
} else { | |
//my_conn.disconnect(); | |
Serial.println("Connecting again…"); | |
if (my_conn.mysql_connect(server_addr, 3306, user, password)) { | |
delay(500); | |
Serial.println("Success!"); | |
} else { | |
num_fails++; | |
Serial.println("Connect failed!"); | |
if (num_fails == MAX_FAILED_CONNECTS) { | |
Serial.println("No MySQL server available…"); | |
delay(2000); | |
} | |
} | |
} | |
// close the database connection | |
my_conn.disconnect(); | |
} | |
} | |
// Just prints the time so we know we are not stuck | |
Serial.print("Current time is "); | |
print2digits(tm.Hour); | |
Serial.write(':'); | |
print2digits(tm.Minute); | |
Serial.write(':'); | |
print2digits(tm.Second); | |
Serial.println(); | |
// Basically we check the time every 5 seconds. You can increase this | |
delay(5000); | |
} |
Some explanations:
First, we try to obtain an IP address for our Arduino project using DHCP. If everything is OK we then go to a Network Time Protocol server to obtain the current time and date. This is done only once, when the power is applied, to avoid overloading the NTP server, as this is a very important issue. You can read more about the problems related to abusing NTP servers in the Arduino forums.
The NTP routine first resolves ro.pool.ntp.org to obtain the IP address of an NTP server. Please observe that I’m using the national pool of NTP servers instead of the global one. More information on NTP servers can be found on http://www.pool.ntp.org/en/.
Once we have the IP address of a working NTP server we get the time. For this I used a slightly modified version of the NTP code provided by OpenReefs.
The DS1307RTC library requires the time to be stored in the tm structure. Most important, the years are measured starting with 1970 – thus the need to subtract this from the value returned by the year() function. We then call the RTC.write() function to update the RTC timer.
We wish to write into the MySQL database every 10 minutes. For this, we use two variables of integer type, minutes_now which store the current minute and minutes_next which stores the time for the next database operation. We read the RTC time every 5 seconds. If minutes_now equals minutes_next we first compute the next value for minutes_next, then we measure the temperature and humidity, and we write the time and the recorded values into the MySQL database.
To insert the data into the database we first connect to the MySQL database. Just for testing we show the last 5 entries. To insert the data we have to construct the query string. Special attention must be given to the time and date fields. Insert a space into the wrong position and you will have a MySQL error. The workaround was to convert the date and time to a string, then I used the trim() function to remove the spaces. Further, I converted the strings into character arrays, which are later used to construct the query. The temperature and humidity data are truncated to two decimal places, thus precision of the recorded data is around 1%.
We then run the query and we check the result by reading the last entry from the database. Finally, we close the database connection – this is useful if you log the data at large intervals.
The final code is quite large, occupying a huge area of both ROM and RAM memories. In the future I intend to create a smaller version of this code, to run on Arduino Uno boards.
4 Comments
Can you tell me what is a data logger?
https://en.m.wikipedia.org/wiki/Data_logger
This is very interesting and useful project. Is that running MySQL the reason for so much memory demanding?
Hi!
The Ethernet library takes a lot of space, and the MySQL connector requires that library.
Furthermore, I don’t how much optimization was done in the MySQL code. I was happy that it works, and it’s simpler for me to move to an Arduino Mega if I need more memory 🙂