Monday, June 2, 2014

Data Log Code - Arduino Web Logger

What does this code do...?


  1. Reads ADAM-4017's channel 0 every 500 ms.
  2. Store the data as float in buffer.
  3. After repeating 60 * 2 = 120 times, a minute long Data Block has been created.  Write the Data Block to SD.

Can we use the SD library...?

In order to use SD FAT32 we absolutely have to use SD Library.  However, do we have enough memory?

Let's check the size of Arduino UNO's RAM size with using just following code.
void setup() {}
void loop() {}

RAM: 2,048

9#include <SPI.h>
71#include <SPI.h> <Ethernet.h>
262#include <SPI.h> <Ethernet.h> <WebSocket.h>
330#include <SPI.h> <Ethernet.h> <WebSocket.h> <SoftwareSerial.h>
330#include <SPI.h> <Ethernet.h> <WebSocket.h> <SoftwareSerial.h> <Time.h>
1,109#include <SPI.h> <Ethernet.h> <WebSocket.h> <SoftwareSerial.h> <Time.h> <SD.h>

It may seem as if there might not be any problem, but let's check what really happens in actual code from Arduino code - 4017 Arduino Gateway posting.

1,126#include <SPI.h> <Ethernet.h> <WebSocket.h> <SoftwareSerial.h> <Time.h> excluding <SD.h>

If we include setTime() to actually use <Time.h>, the memory becomes 1,163

And then we include <SD.h>...1,769
Open a file...1,863

We only have 212 bytes left.  Now we don't even have 512 bytes to create a Data Block.  Even if we don't create Data Block, how are we going to manage Stack & Heap with this memory?  What memory are we going to use for additional codes?
It might work sometimes... ( doesn't actually run on Arduino )

Therefore, we are going to give up on FAT32.


Now, are we going to use Arduino with bigger RAM?
http://arduino.cc/en/Main/ArduinoBoardMega2560
The biggest they have is 8K anyways.
If we have to improve on hardware continuously, why use Arduino?
Thus alternatively we are going to leave out FAT32 file system.

We are going to use 512 bytes block without using File.  Why do we need File if we are only going to log?

In Arduino library copy the following 4 files into the current sketch folder.
\arduino-1.5.6-r2\libraries\SD\src\utility\Sd2Card.cpp
Sd2Card.h
Sd2PinMap.h
SdInfo.h

Now we are only going to use the following 3 functions.
  • init()
  • readBlock()
  • writeBlock()

Current Time

Eventually we are going to use RTC; however, for this code we are using System Clock.
Library is available on the following site: http://www.pjrc.com/teensy/td_libs_Time.html
Download the Time.zip file unzip it and then Sketch > Import Library > Add Library.

You can also find it in Arduino Cookbook pg. 404.

Source Code

This is only part of the code.  I will post the link to the full code at the bottom.
#include <SPI.h>
#include <Ethernet.h>
#include <WebSocket.h>
#include <SoftwareSerial.h>
#include "Sd2Card.h"
#include <Time.h>

byte mac[] = { 0x90, 0xA2, 0xDA, 0x0F, 0x25, 0xC4 };
byte ip[] = { 192, 168, 219, 16 };

WebSocket wsServer;

// ------------------------------------------- SD Card
#define SD_BASE_BLOCK  1024 // 1024 is not a special #
#define LOG_BLOCK_SIZE 120

byte sdBuffer[512]; // SD card only writes in 512 bytes block.
                    // Thus we have to have 512 bytes.
byte sdStatus = 0;
byte LogblockSize = 0;
uint32_t currBlock;
long lastPollTime = 0;

Sd2Card sdCard;

void SDbegin(void);
void makeLogBlock(void);
void update_currBlock(void);

void setup() {
 Serial.begin(9600);

 Soft485.begin(9600);
 pinMode(DE485, OUTPUT);
 ADAM4017_config();

 Ethernet.begin(mac, ip);
 wsServer.registerConnectCallback(&onConnect);
 wsServer.registerDataCallback(&onData);
 wsServer.registerDisconnectCallback(&onDisconnect);
 wsServer.begin();
 delay(100);

 rx485ix = -1;

 // setTime(hours, minutes, seconds, days, months, years);
 // Time is not going to be right until we add RTC
 setTime(12, 0, 0, 11, 5, 14);
                               
 SDbegin();
 if(!sdStatus)
  printProgln(SD_FAIL_MSG);
}

void loop() {
 wsServer.listen();

 if(sdStatus) {
  if((millis() - lastPollTime) >= 500) {
   lastPollTime = millis();
   AdamPoll('0');
  }
 }

 AdamReceiver();
}

/*
 -----------------------------------------------------
 D A T A  B L O C K  M A K I N G
 -----------------------------------------------------
*/

// ---------------------------------------------------
// SD Card Initialize
// ---------------------------------------------------
void SDbegin(void) {
 byte *pB;

 sdStatus = 0;
 pinMode(10, OUTPUT); // SS

 if(!(sdCard.init(SPI_HALF_SPEED, 4)))
  return; // SD Card Initialization Failed

 if(!(sdCard.readBlock(SD_BASE_BLOCK, sdBuffer)))
  return; // SD Card Read Failed

 pB = sdBuffer;

 // No Used Flag. New SD Card!!
 if(*((uint32_t*)pB) != 0xaa55a55a) {
  printProgln(NEW_SD_MSG);
  *((uint32_t*)pB) = 0xaa55a55a;
  currBlock = SD_BASE_BLOCK + 1;
  *((uint32_t*)(pB + 4)) = currBlock;

  if(!(sdCard.writeBlock(SD_BASE_BLOCK, sdBuffer)))
   return; // SD Card Write Failed
 }

 else
  currBlock = *((uint32_t*)(pB + 4)); // First Free Block

 LogBlockSize = 0;
 sdStatus++;
 printCurrBlock(); // debugging purpose
}

// ---------------------------------------------------
// Create DATA Logging Block
// ---------------------------------------------------
void makeLogBlock(void) {
 byte i;
 byte *pB;
 float fdata;

 // --------- Reset buffer if it is the first time.
 if(!LogBlockSize) {
  pB = sdBuffer + 8; // insert Used Flag &
                     // time-stamp when finished
  fdata = -99.999 * 10.; // -99.999 means that there is no
                         // data. When we read them we do
                         // /10 thus we have to do *10.
  i = 120; // fill it in 120 times
  
  while(i--) {
   *((float*)pB) = fdata;
   pB += 4;
  }

  i = 24; // 24 bytes padding
  
  while(i--) {
   *pB = 0x00;
  }
 }
 /*
  ADAM-4017 returns +dd.ddd
  It uses fixed 3 decimal points.

  To convert decimal string to float:
  double atof(const char* nptr)
  http://www.nongnu.org/avr-libc/user-manual/group__avr__stdlib.html#ga689c9d3c4c04463aa31d329937789d06

  But there is a problem. It only works with 2 decimal points.
  It rounds up third decimal point.
  Thus atof("12.345") will become 12.35.

  As a solution we do *10 then convert it to float.
  0123456    0123456
  +98.765 -> +987.65
 */

 // ----------------------- Insert Data into buffer
 //             0123456
 // rx485Ln[] ">+dd.ddd" <cr>
 rx485Ln[3] = rx485Ln[4];
 rx485Ln[4] = '.'; // *10

 pB = sdBuffer + 8 + (LogBlockSize * 4);
 *((float*)pB) = atof(rx485Ln);

 LogBlockSize++;

 // -------------------------------- buffer is full
 if(LogBlockSize >= LOG_BLOCK_SIZE) {
  pB = sdBuffer;
  *((uint32_t*)pB) = 0xaa55a55a; // Used Flag
  pB += 4;
  *((time_t*)pB) = now(); // Time Stamp

  sdCard.writeBlock(currBlock, sdBuffer);
  currBlock++;
  LogBlockSize = 0;
  update_currBlock(); // Fix the next free block location
 }
}

void update_currBlock(void) {
 byte *pB;
 byte status = 0;

 if(sdCard.readBlock(SD_BASE_BLOCK, sdBuffer)) {
  pB = sdBuffer;

  if(*((uint32_t*)pB) == 0xaa55a55a) {
   *((uint32_t*)(pB + 4)) = currBlock;

   if(sdCard.writeBlock(SD_BASE_BLOCK, sdBuffer)) {
    status++;
    printTOD(); // display time stamp
    printCurrBlock(); // display next free block
   }
  }
 }

 if(!status)
  // something is going terribly wrong
  printProgln(BLOCK_UPDATE_FAIL_MSG);
}

void printTOD(void) {
 tmElements_t tm;

 breakTime(now(), tm);

 Serial.print(tm.Year - 30); // size 1970
 printProg(DASH_MSG);
 Serial.print(tm.Month);
 printProg(DASH_MSG);
 Serial.print(tm.Day);
 printProg(SPACE_MSG);
 Serial.print(tm.Hour);
 printProg(COMMA_MSG);
 Serial.print(tm.Minute);
 printProg(COMMA_MSG);
 Serial.print(tm.Second);
}

void AdamReceiver(void) {
 char rxChr;

 if(Soft485.available()) {
  rxChr = Soft485.read();

  if(rxChr == '>')
   rx485ix = 0;
  
  else if(rx485ix >= 0) {
   if(rxChr == 0x0d) { //     0123456
    if(rx485ix == 7) { // >+dd.ddd<cr>
     wsServer.send(rx485Ln, 7);
     
     if(sdStatus)
      // when AD read value arrives from
      // ADAM-4017, log it
      makeLogBlock();
    }
    rx485ix = -1;
   }

   else {
    rx485Ln[rx485ix++] = rxChr;

    if(rx485ix > 7)
     rx485ix = -1;
   }
  }
 }
}

Full codes can be found in following links