Temperature Logger to SD Card with menu control circuit diagramFigure 1: Temperature Logger to SD Card with menu control circuit diagram

An SD Card can be used to log data continuously over time for various purposes, this project shows the design of a temperature data logger with menu control system. 
The ambient temperature is read every minute and stored in a file on an SD card. 
When the program starts, the user is given three options: 

  • Option 1: Saving the temperature readings to a new file on an SD card (a new text file will be created: “TEMP.TXT”)
  • Option 2: Appending the temperature readings to an existing file on an SD card (Open the existing text file “TEMP.TXT” and continue saving from the last data)
  • Option 3: Sending all the saved file contents to a PC.

The user will have to choose one option and then press enter to start logging data. This menu only accessible once when the program starts, if the user wants to choose another option, he/she has to restart the program 
by pressing the reset button.

Circuit diagram

The circuit diagram above on figure 1 shows an SD Card interfaced to PORTC of PIC18F45K22, an LCD 
display to PORTB and a MAX232 level converter connected to the PIC UART module (RC6 and RC7) to communicate with a PC. 
The CS pin of the card is connected to RC2 of the PIC, Din of card to Do1 (RC5) of PIC, Do of card to 
Di1 (RC4) of card and CLK of card to CLK1 of PIC (RC3).
The power connections of the card are not shown here, but should be connected to the 3.3V variable 
regulator 
(U4). VDD and VSS of the pic microcontroller are not shown in the circuit diagram. VDD should be connected
to +5V and VSS to GND. An 8MHz crystal oscillator is connected OSC1 and OSC2 of the PIC.        

Variable voltage regulator

Figure 2: Variable voltage regulator

Please refer to the articles: 

Interfacing SD Card with PIC Microcontroller

Interfacing LCD Display with PIC microcontroller

Analog to Digital Converter in PIC Microcontrollerand 

PIC Microcontroller Communication with RS232 for more information on hardware connection and software routines.

Prototyping on PCB is always robust with less wires due to poor connections. For rapid development, you can buy an SD card board that you can plug directly into you development board or PCB headers. You can buy the components of this project from our Shop page.

The other and probably most important is also the cost to manufacture your PCBs. The quality of a PCB can affect the overall quality of the whole electronic device.

There are many PCB companies in the world that can make you good quality PCBs, but we need one that can produce cheap boards of high quality on small orders because the first batch of any project is likely be of small quantity. One of those companies we can recommend is PCBWay.

PCBWay is a china based PCB manufacturer specializing in PCB prototyping, small volume production and PCB assembly service all under one roof with more than a decade of experience. You can get 10 high quality PCBs (size: 100mm x 100mm or smaller) for just $5 USD. You can pick between these solder mask colours at no extra cost: Green, Red, Yellow, Blue, white and Black. Please read this article to learn more about PCBWay:

Low Cost PCB Prototype and Assembly with PCBWay.

And if you are a student or an educator, you could get PCB for free, read this article to find out how:

Free PCBs and Discounts for Students and Educators with PCBWay’s Sponsorship Program.

Alternatively you can contact them from their website, click on the logo below:

PCBWay Website

MikroC Code

In this project, a file called TEMPTXT is created on the SD card to store the temperature readings (the library 
function call will insert the “.” to make the filename “TEMP.TXT”), if it does not already exist. 

//
char filename[] = "TEMPTXT";             
// MMC module connections
sbit Mmc_Chip_Select at LATC2_bit;  // for writing to output pin always use latch (PIC18 family)
                                                                          
sbit Mmc_Chip_Select_Direction at TRISC2_bit;

The following functions are created at the beginning of the program, before the main program:

Newline sends a carriage return and a line feed to the UART so the cursor moves to the next line.

//
// This function sends carriage-return and line-feed to UART
        void Newline()
          {
            UART1_Write(0x0D);    // Send carriage-return
            UART1_Write(0x0A);    // Send line-feed
          }

Space insert a space between characters. 

//
// This function sends a space character to UART
      void Space()
        {
          UART1_Write(0x20);
        }

Text_To_UART receives a text string as its argument and sends it to the UART to display on the PC screen. 

//
// This function sends a text to serial port
    void Text_To_UART(unsigned char *x)
      {
        unsigned char i;
         i = 0;
         while(x[i] != 0)
      {
   // Send TEXT to serial port
       UART1_Write(x[i]);
        i++;
         }
       }

Read_Temperature starts the A/D conversion and receives the converted data into a variable called VoltIn. The voltage corresponding to this value is then calculated in millivolts and divided by 10 to find the actual measured temperature in °C. The decimal part of the found temperature is then converted into string form using function LongToStr. The leading spaces are removed from this string, and the resulting string is stored in character array temperature. 

Then the fractional parts of the measured temperature, a carriage return, and a line feed are added to this 
character array, which is later written to the SD card.

//
 // This function reads the temperature from analog input E1
    void Read_Temperature()
      {
        unsigned long VoltIn, VoltDec,VoltFrac;
        unsigned char voltString[12];
        unsigned char i,j;
        VoltIn = Adc_Read(6);            // Read from channel 6 (PORTE1) (AN6)
        VoltIn = 488*VoltIn;               // Scale up the result
        VoltIn = VoltIn /10;                // Convert to temperature in Celcius
        VoltDec = VoltIn / 100;           // Decimal part
        VoltFrac = VoltIn % 100;         // Fractional part
        LongToStr(VoltDec,voltString);  // Convert VoltDec to string in "voltString"
 
     // Remove leading blanks
         j=0;
         for(i=0;i<=11;i++)
            {
              if(op[i] != ' ')                     // If a there is a blank space
              {
                temperature[j]=op[i]; j++;
               }
             }
           temperature[j] = '.';            // Add “.”
           ch1 = VoltFrac / 10;            // fractional part
           ch2 = VoltFrac % 10;
            j++;
            temperature[j] = 48+ch1;   // Add fractional part
            j++;
            temperature[j] = 48+ch2;
            j++;
            temperature[j] = 0x0D;      // Add carriage return
            j++;
            temperature[j] = 0x0A;     // Add line-feed
            j++;
            temperature[j]='';
          }

Inside the main program, the following operations are performed:

     Initialize LCD Display, 
     Initialize the UART to 9600 baud, 
     Initialize the SPI bus, 
     Initialize the FAT file system, 
     Display menu on the PC screen,
     Get a choice from the user (1, 2, or 3):

  • If the choice is 1, create a new temperature file, get new temperature readings every minute, and store them in the file 
  • If the choice  is 2, assign to the existing temperature file, get new temperature readings every minute and append them to the existing temperature file
  • If the choice is 3, assign the temperature file, read the temperature records, and display them on the PC screen. 
  • If the choice is not 1, 2, or 3, display an error message on the screen

The project can be tested by connecting the output of the microcontroller to the serial port of a PC (e.g., 
COM1) and then running the HyperTerminal terminal emulation software or any other serial terminal software
If you are using Microsoft windows XP, Go to Start, All Programs, Accessories, Communications and Hyper 
Terminal to open a windows free Hyper Terminal. 
Set the communications parameters to 9600 baud, 8 data bits, 1 stop bit, and no parity bit. Figure 3 below 
shows a snapshot of the PC screen when Option 2 is selected to save the temperature records in a new file.                

Snapshot of PC Screen

Figure 3: Snapshot of PC Screen

Full project Source Code

/*..............................................................
Temperature Logger to SD Card with PC menu
===============
In this project a SD card is connected to PORTC as follows:
CS RC2
CLK RC3
DO RC4
DI RC5
This project shows the design of a temperature data logger system. The ambient temperature is read and stored in a file on an SD card. 
The program is menu-based. The user is given options of either
to send the saved temperature data to the PC, or to read and
save new data on the SD card, or to read temperature data and
append to the existing file. Temperature is read at every minute.

The temperature is stored in a file called "TEMP.TXT"
Author: studentcompanion.co.za
File: Temperature_Logger.C
..............................................................*/

//Global variables
char filename[] = "TEMPTXT";
unsigned short character;
unsigned long file_size,i,rec_size;
unsigned char ch1,ch2,flag,ret_status,choice;
unsigned char temperature[10],txt[12];

// Lcd pinout settings
sbit LCD_RS at RB4_bit;
sbit LCD_EN at RB5_bit;
sbit LCD_D7 at RB3_bit;
sbit LCD_D6 at RB2_bit;
sbit LCD_D5 at RB1_bit;
sbit LCD_D4 at RB0_bit;

// Pin direction
sbit LCD_RS_Direction at TRISB4_bit;
sbit LCD_EN_Direction at TRISB5_bit;
sbit LCD_D7_Direction at TRISB3_bit;
sbit LCD_D6_Direction at TRISB2_bit;
sbit LCD_D5_Direction at TRISB1_bit;
sbit LCD_D4_Direction at TRISB0_bit;

// This function sends carriage-return and line-feed to UART
void Newline()
{
UART1_Write(0x0D); // Send carriage-return
UART1_Write(0x0A); // Send line-feed
}

// This function sends a space character to USART
void Space()
{
UART1_Write(0x20);
}

// This function sends a text to serial port
void Text_To_UART(unsigned char *x)
{
unsigned char i;
i = 0;
while(x[i] != 0)
{ // Send TEXT to serial port
UART1_Write(x[i]);
i++;
}
}

// This function reads the temperature from analog input E1
//
void Read_Temperature()
{
unsigned long VoltIn, VoltDec,VoltFrac;
unsigned char voltString[12];
unsigned char i,j;
VoltIn = Adc_Read(6);           // Read from channel 6 (PORTE1) (AN6)
VoltIn = 488*VoltIn;            // Scale up the result
VoltIn = VoltIn /10;            // Convert to temperature in C
VoltDec = VoltIn / 100;         // Decimal part
VoltFrac = VoltIn % 100;        // Fractional part
LongToStr(VoltDec,voltString);  // Convert VoltDec to string in "op"

// Remove leading blanks
j=0;
for(i=0;i<=11;i++)
{
if(voltString[i] != ' ')         // If there is a blank
{
temperature[j]=voltString[i];
j++;
}
}
temperature[j] = '.';      // Add “.”
ch1 = VoltFrac / 10;      // fractional part
ch2 = VoltFrac % 10;
j++;
temperature[j] = 48+ch1; // Add fractional part
j++;
temperature[j] = 48+ch2;
j++;
temperature[j] = 0x0D;  // Add carriage-return
j++;
temperature[j] = 0x0A;  // Add line-feed
j++;
temperature[j]='\0';
}

// MMC module connections
sbit Mmc_Chip_Select           at LATC2_bit;  // for writing to output pin always use latch (PIC18 family)
sbit Mmc_Chip_Select_Direction at TRISC2_bit;


void main()
{
  ANSELB = 0;                        // Configure PORTB pins as digital I/O
  ANSELC = 0;                      // Configure PORTC pins as digital  I/O
  SLRCON = 0;                      // Configure all PORTS at the standard Slew Rate
  rec_size = 0;
//
// Configure A/D converter
//
TRISE =  0xFF;                      //Configure PORTE as input
ANSELE = 0x40;                     // The PORTE.1 pin is configured as analog
ADCON1 = 0x80;                     // Use Vref = +5V

// Configure the serial port
UART1_Init(9600);

// Initialise LCD Display
Lcd_Init();
Lcd_Cmd(_LCD_CURSOR_OFF);          // Cursor off

// Initialise the SPI bus
SPI1_Init_Advanced(_SPI_MASTER_OSC_DIV16,_SPI_DATA_SAMPLE_MIDDLE,
_SPI_CLK_IDLE_LOW, _SPI_LOW_2_HIGH);

// Initialise the SD Card
// use fat16 quick format instead of init routine if a formatting is needed
  if (Mmc_Fat_Init() == 0) {
    // reinitialize spi at higher speed
    SPI1_Init_Advanced(_SPI_MASTER_OSC_DIV4, _SPI_DATA_SAMPLE_MIDDLE, _SPI_CLK_IDLE_LOW, _SPI_LOW_2_HIGH);
    }
    
Lcd_Cmd(_LCD_CLEAR);              //Clear LCD
Lcd_Out(1, 1, "TEMP LOGGER");
Lcd_Out(2, 3, "Starting.");
// Display the MENU and get user choice
//
Newline();
Text_To_UART("TEMPERATURE DATA LOGGER TO SD CARD");
Newline();
Text_To_UART("Please select one from the following:");
Newline();
Newline();
Text_To_UART("1. Save temperature readings in a new file");
Newline();
Text_To_UART("2. Append temperature data to an existing file");
Newline();
Text_To_UART("3. Send temperature data to the PC");
Newline();
Newline();
Text_To_UART("Choice ? ");

// Read a character from the PC keyboard
flag = 0;
do {
if (UART1_Data_Ready())   // If data received
{
choice = UART1_Read();    // Read the received data
UART1_Write(choice);      // Echo received data
flag = 1;
}
} while (!flag);
Newline();
Newline();

//processing the choices
switch(choice)
{
case '1':
// Start the A/D converter, get temperature readings every minute
//and then save in a NEW file
Text_To_UART("Saving data in a NEW file...");
Newline();
Mmc_Fat_Assign(&filename,0x80); // Assign the file
Mmc_Fat_Rewrite();              // Clear
Mmc_Fat_Write("TEMPERATURE DATA TO BE SAVED EVERY MINUTE\r\n",43);
//
// Read the temperature from A/D converter, format and save
//
for(;;)
{
Mmc_Fat_Append();
Read_Temperature();
Mmc_Fat_Write(temperature,9);
rec_size++;
LongToStr(rec_size,txt);
Newline();
Text_To_UART("Saving record:");
Text_To_UART(txt);
Lcd_Cmd(_LCD_CLEAR);                 //Clear LCD
Lcd_Out(1, 1, "Temp is: ");          //Display Current temperature in degree celcius to LCD
Lcd_Out(1, 11, temperature);
Delay_ms(60000);
}
break;

case '2':
// Start the A/D converter, get temperature readings every minute
//and then APPEND to the existing file
//
Text_To_UART("Appending data to the existing file...");
Newline();
ret_status = Mmc_Fat_Assign(&filename,1); // Assign the file
if(!ret_status)
{
Lcd_Cmd(_LCD_CLEAR);              //Clear LCD
Lcd_Out(1, 5, "Error!");
Lcd_Out(2, 1, "Please Restart..");
Text_To_UART("File does not exist - can not append...");
Newline();
Text_To_UART("Restart the program and choose option 2 to save data in new file...");
Newline();
for(;;);
}
else
{
//
// Read the temperature from A/D converter, format and save

   for(;;)
{
Mmc_Fat_Append();
Read_Temperature();
Mmc_Fat_Write(temperature,9);
Lcd_Cmd(_LCD_CLEAR);                 //Clear LCD
Lcd_Out(1, 1, "Temp is: ");          //Display Current temperature in degree celcius to LCD
Lcd_Out(1, 11, temperature);
rec_size++;
LongToStr(rec_size,txt);
Newline();
Text_To_UART("Appending new record:");
Text_To_UART(txt);
Delay_ms(60000);
}
case '3':
ret_status = Mmc_Fat_Assign(&filename,1);
if(!ret_status)
{
Lcd_Cmd(_LCD_CLEAR);              //Clear LCD
Lcd_Out(1, 5, "Error!");
Lcd_Out(2, 1, "Please Restart..");

Text_To_UART("Error! File does not exist/ no saved data.");
Newline();
Text_To_UART("Please Restart the program and save data to file.");
Newline();
for(;;);
}
else
{
//
// Read the data and send to UART
Lcd_Cmd(_LCD_CLEAR);              //Clear LCD
Lcd_Out(1, 3, "Sending data...");
Text_To_UART("Sending all saved data to PC");
Newline();
Mmc_Fat_Reset(&file_size);
for(i=0; i<file_size; i++)
{
Mmc_Fat_Read(&character);
UART1_Write(character);
}
Newline();
text_To_UART("End of data. Thank you.");
Newline();
for(;;);
}
}
default:
Text_To_UART("You made a wrong choice, please restart the program.");
Newline();
for(;;);
}
}

You can download the full project files (MikroC source code and Proteus Schematic design) below here. 
All the files are zipped, you will need to unzip them (
Download a free version of the Winzip utility to unzip files).

Download Temperature Logger MikroC Code

Download Temperature Logger Proteus