Here's the full code (and I hope I'm doing this right ...). Note that code is being developed in MS Visual Studio with Arduino extension.
Purpose of this program is to use an Arduino pro mini to capture a serial bit bitstream from a hobby (LiPo, NimH, Pb, etc) charger and store the data as CSV file on an SD card.
A pushbutton is used to instruct the Arduino to start/stop datalogging (works fine)
The data from the charger is received as a 100 hex digit stream (by function RcvHexStringFromDatalogSerial - this capture works fine).
Function ConvertHexString2Int converts each hex field (from RcvHexString) to an integer:
- 2 arrays with int pointers to point where each field starts & how wide each field is.
- recursively call hex2int to convert each hex digit to int (works fine too)
Write to SD has been implemented for the hex data, not yet for the CSV data.
In below code the for loop is commented out and replaced by 27 individual code lines (the for loop crashes the Arduino - the individual lines work ???)
/*
Name: DataLogger.ino
Created: 5/3/2020 1:37:22 PM
Author: Johan
*/
#include <SoftwareSerial.h>
#include <string.h>
#include <SdFat.h>
#include <SPI.h>
#define max_logfiles 99
#define SerialBufferSize 64
#define datalog_buffersize 128
#define datalog_int_fields 27
// Global variables using the SD utility library functions:
SdFat sd;
File fpDataFile;
// SPI/SD pins
#ifdef ARDUINO_AVR_PRO
const uint8_t SD_chipSelect = 10;
#else ifdef ARDUINO_AVR_DUEMILANOVE
const uint8_t SD_chipSelect = 4;
#endif
const uint8_t SPI_SS = 10; // No idea if this pin is actually used on Due or Pro Mini
const uint8_t SPI_MOSI = 11;
const uint8_t SPI_MISO = 12;
const uint8_t SPI_SCK = 13;
// Other pins
const uint8_t buttonPin = 8;
const uint8_t LEDPin = 7;
// Variables to keep track of the button/dlog states
bool b1down = false;
bool dlog_state = false;
const uint8_t DataRxPin = 6; // Serial port to charger
const uint8_t DummyTxPin = 5; // TX not used, but required by SoftwareSerial
SoftwareSerial DatalogSerial(DataRxPin, DummyTxPin, 1); //RX, T
void setup() {
// ---------------- Run once ----------------------
// the setup function runs once when you press reset or power the board
//button pin modes
pinMode(buttonPin, INPUT_PULLUP); //button1 func
pinMode(LEDPin, OUTPUT); //LED func
pinMode(DataRxPin, INPUT_PULLUP); //Data func
pinMode(DummyTxPin, OUTPUT);
digitalWrite(LEDPin, LOW);
digitalWrite(DummyTxPin, LOW);
Serial.begin(57600); // initialize USB serial:
delay(200);
Serial.println();
Serial.println("-------------------------------------------------");
Serial.println("Arduino SD Datalogger connected OK to serial port");
Serial.println("-------------------------------------------------");
card_info(SD_chipSelect);
}
void loop() {
// ---------------- Main loop ---------------------
// the loop function runs over and over again until power down or reset
bool ButtonPressed;
char SerialBuffer[SerialBufferSize]; // Array to store messages
// With this buffer one can create more complex lines than can be done with std serial.println
sprintf(SerialBuffer, "Loop - ");
Serial.print(SerialBuffer);
ButtonPressed = areButtonsPressed();
// Serial.print("ButtonPressed: ");
// Serial.println(ButtonPressed);
wait(10);
if (ButtonPressed == true) {
if (dlog_state == true) {
Serial.println("End_datalog");
end_datalog();
dlog_state = false;
digitalWrite(LEDPin, LOW);
DatalogSerial.end(); // End 2nd serial stream of datalog data
}
else {
Serial.println("Init_datalog");
dlog_state = true;
digitalWrite(LEDPin, HIGH);
DatalogSerial.begin(9600); // Setup card 2nd serial stream of datalog data
init_datalog();
}
}
else {
if (dlog_state == true) {
Serial.println("Continue_datalog");
continue_datalog();
}
else { Serial.println("Do nothing"); }
}
}
bool areButtonsPressed() {
//handle buttons -------------------------------------------
bool ButtonPressed = false;
// detect button down press (digitalRead(buttonPin)==0)
// only move to next step when button is released (digitalRead(buttonPin)==1)
if (b1down == false && digitalRead(buttonPin) == 0) {
b1down = true;
ButtonPressed = true;
}
// detect button up/release
else if (b1down == true && digitalRead(buttonPin) == 1) {
b1down = false;
}
// Serial.print("buttonstate: ");
// Serial.println(b1down);
return ButtonPressed;
}
// ---------------- SD Card functions ----------------------
void card_info(uint8_t SD_CS_pin) {
if (!sd.begin(SD_CS_pin, SPI_FULL_SPEED)) {
Serial.println("SD Card initialization failed - things to check:");
Serial.println("* is an SD card inserted?");
Serial.println("* is SD power switched ON?");
// while (1); // loop eternally - user to take action and restart
sd.initErrorHalt();
}
else {
Serial.println("Card is present.");
//Class sd.card
// init
// type
// readCID
// readCSD
// print the type of card
Serial.print("Card type: ");
switch (sd.card()->type()) {
case SD_CARD_TYPE_SD1:
Serial.println("SD1");
break;
case SD_CARD_TYPE_SD2:
Serial.println("SD2");
break;
case SD_CARD_TYPE_SDHC:
Serial.println("SDHC");
break;
default:
Serial.println("Unknown");
}
// Skip the rest as all the messages take too much SRAM memory
// char Serial_string[80];
// unsigned int blocks;
// unsigned long int clusters;
// unsigned long int volumesize;
// float volumesize_Gb;
//Class SD2card
// init
// type
// readCID
// readCSD
//Class SDvolume
// init
// fatType
// blocksPerCluster
// clusterCount
// Now we will try to open the 'volume'/'partition' - it should be FAT16 or FAT32
//if (!sd.vol()->fatType()) {
// Serial.println("Could not find FAT16/FAT32 partition. Make sure you've formatted the card\n");
//}
// Block: minimal addressable block - defined by SD hardware - 512 bytes
// Cluster: groups a number of blocks - defined during FAT formatting
// Volume: contains lots of clusters
// blocks = volume.blocksPerCluster();
// clusters = volume.clusterCount();
// sprintf(Serial_string, "Blocksize: 512 (bytes - SD card hardware defined)\n"); Serial.print(Serial_string);
// sprintf(Serial_string, "Blocks per cluster: %d (SD card formatting defined)\n", blocks); Serial.print(Serial_string);
// sprintf(Serial_string, "Clusters: %ld\n", clusters); Serial.print(Serial_string);
// sprintf(Serial_string, "Total size (kB): (blocks * clusters) / (blocksize/2) = %ld\n\n", volume.blocksPerCluster() * volume.clusterCount() / 2); Serial.print(Serial_string);
//Class SDvolume
// fatType
// blocksPerCluster
// clusterCount
// print the FAT-type
// Serial.print("Volume type is: FAT");
// Serial.println(sd.vol()->fatType(), DEC);
// list all files in the card with date and size
// sd.ls(LS_R | LS_DATE | LS_SIZE);
// volumesize = volume.blocksPerCluster(); // clusters are collections of blocks
// volumesize *= volume.clusterCount(); // we'll have a lot of clusters
// volumesize /= 2; // SD card blocks are always 512 bytes (2 blocks are 1KB)
// sprintf(Serial_string,"Volume size: %ld (kB)\n",volumesize); Serial.print(Serial_string);
// volumesize /= 1024;
// sprintf(Serial_string, "Volume size: %ld (MB)\n", volumesize); Serial.print(Serial_string);
// Arduino sprintf does not support printing floats
// Serial.print("Volume size: ");
// Serial.print((float)volumesize / 1024.0);
// Serial.println("(GB\n");
Serial.println("Card info done");
}
}
// ---------------- Datalog fuctions ----------------------
void init_datalog() {
uint8_t i;
char SerialBuffer[SerialBufferSize];
char datalog_filename[15];
// check for existing files
// filename can not be longer than 8.3
// increment the no if already present (max 99)
for (i = 0; i < max_logfiles; i++) {
snprintf(datalog_filename, sizeof(datalog_filename), "Dlog_%2.2i.log", i);
if (sd.exists(datalog_filename)) {
sprintf(SerialBuffer, "File %s exists", datalog_filename); Serial.println(SerialBuffer);
}
else {
// open the file
fpDataFile = sd.open(datalog_filename, FILE_WRITE);
if (fpDataFile) {
sprintf(SerialBuffer, "File %s opened", datalog_filename); Serial.println(SerialBuffer);
i = max_logfiles + 1; // break out of the loop
}
else {
sprintf(SerialBuffer, "Failed to open file %s", datalog_filename); Serial.println(SerialBuffer);
dlog_state = false;
}
}
}
if (i == max_logfiles)
{
// sprintf(SerialBuffer, "Clean up log files, max %d reached", max_logfiles);Serial.println(SerialBuffer);
digitalWrite(LEDPin, LOW);
dlog_state = false;
}
}
void continue_datalog() {
uint8_t i;
uint8_t j;
uint8_t string_end;
char HexDatalogBuffer[datalog_buffersize];
uint16_t IntDatalogBuffer1[datalog_int_fields] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
if (RcvHexStringFromDatalogSerial(HexDatalogBuffer)) {
string_end = strlen(HexDatalogBuffer);
for (j = 0; j <= string_end; j++) {
Serial.print(HexDatalogBuffer[j]);
}
Serial.println("");
// Save raw string to SD
if (string_end > 0) {
ConvertRcvHexString2Int(HexDatalogBuffer, IntDatalogBuffer1);
fpDataFile.write(HexDatalogBuffer);
}
for (i = 0; i < datalog_int_fields; i++) {
Serial.print(IntDatalogBuffer1[i]);Serial.print(";");
}
Serial.println("");
}
}
void end_datalog() {
char SerialBuffer[SerialBufferSize];
char datalog_filename[15];
// close the SD file
fpDataFile.getName(datalog_filename, 15);
sprintf(SerialBuffer, "File %s closed", datalog_filename); Serial.println(SerialBuffer);
fpDataFile.close();
}
bool RcvHexStringFromDatalogSerial(char *RcvHexString) {
// RcvHexStringFromDatalogSerial will:
// * wait until data is being received
// * check if the first character is a FormFeed
// * if yes, continue till the Carriage Return character is received.
// * return to the datalog subroutine
// (as there is a 600ms pauze - use this to process data)
// A frame has a duration of 1second (408ms data, 600ms pauze)
// Since we might start just after the beginning of a data bust
// it can take max. 1.4 seconds to capture a burst
const uint8_t Frame_start_char = 12; // FF
const uint8_t Frame_end_char = 13; // CR + LF
uint8_t i = 0; // serial buffer is max 63
uint8_t bytes_received = 0;
uint8_t k = 0; // one data string is 101 chars long
bool Framestart_found = false;
const int TIMEOUT = 1450;
unsigned long time_now;
char data; // data is received as an 8 bit ASCII coded byte
// wait for the beginning of a log burst
time_now = millis();
while (millis() < time_now + TIMEOUT) {
bytes_received = DatalogSerial.available();
if (bytes_received != 0) {
for (i = 1; i <= bytes_received; i++) { //i starts at 1 to skip the first FF byte
data = DatalogSerial.read();
if (data == Frame_start_char) {
digitalWrite(DummyTxPin, HIGH); // indicate the start of (reading) a data burst
Framestart_found = true;
} else
if (Framestart_found == true) {
RcvHexString[k] = data;
k++;
}
}
if ((Framestart_found == true) && (data == Frame_end_char)) {
digitalWrite(DummyTxPin, LOW); // indicate the end of (reading) a data burst
RcvHexString[k] = '\0';
return true;
break; // end while
}
}
}
if (millis() >= time_now + TIMEOUT) {
Serial.println("RcvHexString: timeout reached, no data received");
Serial.println("Is the charger ON?");
digitalWrite(LEDPin, LOW);
dlog_state = false;
return false;
}
}
// ---------------- Support fuctions ----------------------
void ConvertRcvHexString2Int(char *RcvHexString, uint16_t *IntArray) {
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
const uint8_t field_index[datalog_int_fields] = { 2, 6, 8, 10, 12, 16, 20, 24, 28, 32, 34, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96};
const uint8_t field_width[datalog_int_fields] = { 4, 2, 2, 2, 4, 4, 4, 4, 4, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4};
uint16_t temp_int;
uint8_t i;
// for (i = 0; i < datalog_int_fields; i++) {
// Serial.print(i);
// temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]);
// IntArray[i] = temp_int;
// }
// Serial.print(" - ");
i = 0; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 1; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 2; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 3; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 4; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 5; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 6; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 7; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 8; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 9; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 10; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 11; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 12; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 13; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 14; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 15; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 16; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 17; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 18; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 19; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 20; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 21; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 22; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 23; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 24; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 25; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
i = 26; temp_int = ConvertHexString2Int(RcvHexString, field_index[i], field_width[i]); IntArray[i] = temp_int;
}
uint16_t ConvertHexString2Int(char *HexString, uint8_t FieldIndex, uint8_t FieldWidth) {
char c;
uint8_t i;
uint16_t convertedField = 0;
c = HexString[FieldIndex];
convertedField = Hex2Int(c);
for (i = 1; i < FieldWidth; i++) {
c = HexString[FieldIndex + i];
convertedField = (convertedField * 16) + Hex2Int(c);
}
return convertedField;
}
uint16_t Hex2Int(char hexdigit) {
uint16_t intdigit = 0;
switch (hexdigit) {
case '0': intdigit = 0; break;
case '1': intdigit = 1; break;
case '2': intdigit = 2; break;
case '3': intdigit = 3; break;
case '4': intdigit = 4; break;
case '5': intdigit = 5; break;
case '6': intdigit = 6; break;
case '7': intdigit = 7; break;
case '8': intdigit = 8; break;
case '9': intdigit = 9; break;
case 'A': intdigit = 10; break;
case 'B': intdigit = 11; break;
case 'C': intdigit = 12; break;
case 'D': intdigit = 13; break;
case 'E': intdigit = 14; break;
case 'F': intdigit = 15; break;
default : intdigit = 0;
}
return intdigit;
}
void wait(int w817) {
unsigned long time_now;
time_now = millis();
while (millis() < time_now + w817) {
// do nothing
}
}