precision of floating variables in arduino - arduino

I am taking voltage signal from Analog Input (A0 pin) and current signal (in the form of voltage) from Analog input (A1 pin), converting it to digital and then processing it to get Vrms, Irms and phase data. Then i am storing it in a "dataString" and writing it onto SD card.
The problem i am facing is that somewhere in the floating point computation of the power factor I am doing something wrong due to which the answer is being "displayed as" 1.00 whereas at an angle of 4.97 (degrees) i should get cos(4.97) = 0.9962 (Picture attached)
Image1
Although the program is using the correct value i.e., 0.9962 in further computations ( that of real Power) but i want it to display the power factor correctly upto 4 points after decimal. Here is my program code
#include <SPI.h>
#include <SD.h>
#include <Wire.h>
#include "RTClib.h"
RTC_DS1307 RTC;
#include "DHT.h"
#define DHTPIN 8
#define DHTTYPE DHT21
DHT dht(DHTPIN, DHTTYPE);
#define count 100
const int analogInPin1 = A1;
const int analogInPin0 = A0;
const int chipSelect = 10;
void setup()
{
Serial.begin(115200);
Wire.begin();
RTC.begin();
if (! RTC.isrunning()) {
Serial.println("#RTC is NOT running!");
// following line sets the RTC to the date & time this sketch was compiled
// uncomment it & upload to set the time, date and start run the RTC!
// RTC.adjust(DateTime(__DATE__, __TIME__));
}
analogReference(DEFAULT);
Serial.println("#DHTxx test!");
dht.begin();
Serial.print("#Initializing 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;
}
Serial.println("#card initialized.");
Serial.println("#Date Time Vrms Irms Phase Power_factor Apparent_Power Real_Power Humidity Temperature");
}
void loop()
{
float sensorValue0[count];
float sumSensorValue0=0;
float meanSensorValue0=0;
float Vrms=0;
sumSensorValue0=0;
float sensorValue1[count];
float sumSensorValue1=0;
float meanSensorValue1=0;
float Irms=0;
int i=0;
sumSensorValue1=0;
DateTime now = RTC.now();
for(i=0;i<count;i++)
{
sensorValue1[i] = (analogRead(analogInPin1)*4.8)-3200; //4.8 mV (i.e. 0.0048 Volts) per unit.. Vref/1024.. here Vref = 5 V ....//3.220 V = Offset
sensorValue0[i] = (analogRead(analogInPin0)*4.8)-3200;
sensorValue1[i] = sensorValue1[i]*sensorValue1[i];
sensorValue0[i] = sensorValue0[i]*sensorValue0[i];
sumSensorValue1+= sensorValue1[i];
sumSensorValue0+= sensorValue0[i];
}
meanSensorValue1 = sumSensorValue1/count;
meanSensorValue0 = sumSensorValue0/count;
Irms = (sqrt(meanSensorValue1)*0.06); //60/1000 = 0.06 Calibrating 60 Ampere/1 Volt to give us the value for X amperes
Vrms = (sqrt(meanSensorValue0)*0.3565); // Multiplying with 356.5(the product of ratios of 9V and 12 V transformer) gives the measured voltage in mV.. dividing by 1000 to bring it to Volts from mV
float appPower;
appPower = Vrms*Irms;
float Vsense=0;
float LastVsense=0;
float Isense=0;
float LastIsense=0;
float phase;
float mean_phase=0;
float counter=0;
unsigned long timer;
for(int i=0;i<200;i++)
{
// put your main code here, to run repeatedly:
Isense=analogRead(A1)*4.8-3200;
Vsense=analogRead(A0)*4.8-3220;
if(Vsense>= 0 && LastVsense<0 && Isense<0 )
{
timer = micros();
do{
Isense=analogRead(A1)*4.8-3200;
}while(!(Isense>=0));
timer = micros()-timer;
phase = (timer*360.0)/20000.0;
mean_phase+=phase;
counter+=1.0;
}else;
if(Isense >= 0 && LastIsense < 0 && Vsense < 0 )
{
timer = micros();
do{
Vsense=analogRead(A0)*4.8-3200;
}while(!(Vsense>=0));
timer = micros()-timer;
phase = (timer*360.0)/20000.0;
mean_phase+=phase;
counter+=1.0;
}else;
LastIsense = Isense;
LastVsense = Vsense;
}
mean_phase= mean_phase/counter;
float realPower;
float powerFactor;
float phase_rad= mean_phase*PI/180.0;
powerFactor =cos(phase_rad); //phase converted to radian for cosine function
realPower = Vrms*Irms*powerFactor;
String dataString = "";
float h = dht.readHumidity();
float t = dht.readTemperature();
if (isnan(t) || isnan(h)) {
Serial.println("#Failed to read from DHT");
} else {
dataString+=now.year(), DEC;
dataString+="/";
dataString+=now.month(), DEC;
dataString+="/";
dataString+=now.day(), DEC;
dataString+=" ";
dataString+=now.hour(), DEC;
dataString+=":";
dataString+=now.minute(), DEC;
dataString+=":";
dataString+=now.second(), DEC;
dataString+=" ";
dataString+=Vrms;
dataString+=" ";
dataString+=Irms;
dataString+=" ";
dataString+=mean_phase;
dataString+=" ";
dataString+=powerFactor;
dataString+=" ";
dataString+=appPower;
dataString+=" ";
dataString+=realPower;
dataString+=" ";
dataString+=h;
dataString+=" ";
dataString+=t;
}
// open the file. note that only one file can be open at a time,
// so you have to close this one before opening another.
File dataFile = SD.open("datalog.dat", FILE_WRITE);
// if the file is available, write to it:
if (dataFile) {
dataFile.println(dataString);
dataFile.close();
// print to the serial port too:
Serial.println(dataString);
}
// if the file isn't open, pop up an error:
else {
Serial.println("#error opening datalog.dat");
}
delay(10000);
}

Although the program is using the correct value i.e., 0.9962 in further computations...
That points to the issue being in your printing code.
More specifically, I suspect that this line might be causing trouble:
dataString+=powerFactor;
You are using the String class, so WString.cpp is the relevant file.
If we inspect it, we find that on line 409 (at least, in my version of Arduino, 1.6.7 IIRC), the + operator is declared for floats, and it simply calls concat(float), which can be found on line 323:
unsigned char String::concat(float num)
{
char buf[20];
char* string = dtostrf(num, 4, 2, buf);
return concat(string, strlen(string));
}
If you read the dtostrf docs, you will find that this is converting a double (the float gets promoted) to a string with a width of 4 and 2 digits of precision.
The easiest way to get around this is to use dtostrf to convert the float to a string with the precision you want, and then append that string to your String instance.

Related

Send Arduino serial commands while plotting

I have a simple PD Arduino controller to spin a motor. I want to use it to demonstrate system responses graphically. I have it working so I can give a target position using the serial monitor, but I want to be able to see the serial plot output at the same time. There seems to be a similar dialogue box in the Serial Plotter, but commands sent from there don't seem to be recognized. Is there a way to plot incoming serial data while also sending commands as described above? I don't mind if I need additional libraries, but I can't see why it shouldn't work natively since I can already send commands while receiving info using the Serial Monitor. Maybe I'm misunderstanding that process.
Any help would be very appreciated. See full code below:
// Clockwise rotation direction.
#define CW 1
// Counter clockwise rotation direction.
#define CCW 0
// Frequency of output PWM signal.
#define PWM_FREQ 25000
// Update rate in microseconds.
#define CYCLE_TIME 1000
// Rate of sending position data to PC.
#define PLOT_RATE 200
#define PLOT_COUNTER CYCLE_TIME/PLOT_RATE
// IO pins. //
// The pin connected to ENBble A on the driver.
const int ENB = 14;
// Pins connected to IN3 and IN4 on the driver (for controlling the rotation direction).
const int IN4 = 15;
const int IN3 = 16;
// Signal A wire of the encoder.
const int ENCA = 17;
// Signal B wire of the encoder.
const int ENCB = 18;
// Value of ENCA.
int enca = 0;
// Value of ENCB.
int encb = 0;
// Value of IN3.
int in3 = 0;
// Value of IN4.
int in4 = 0;
// Motors position measure by encoder.
volatile long int motorPos = 0;
// Communication variables. //
// The byte sent over serial to Teensy.
int incomingByte = 0;
// Input buffer for receiving user input over serial.
char inputBuffer[8];
int bufferCnt = 0;
// Counter for sending position over serial for plotting purposes.
int pltCounter = 0;
// Controller variables./ /
// Last motor position.
long int lastPos = 0;
// Target motor position.
int targetPos = 0;
// Position at the start of the control loop.
int currentPos = 0;
// Position at the start of the previous control loop.
int prevPos = 0;
// Change in position (for approximating the derivative).
int dP = 0;
// Position error.
int pError = 0;
// P term of the controller.
int pTerm = 0;
// D term of the controller.
int dTerm = 0;
// Speed (= voltage = duty cycle). Controller output mapped to duty cycle range.
int spd = 0;
// Controller output.
int contOut = 0;
// Ratio for transforming counts to degrees (1920 count / 360 deg)
float ratio = static_cast<float>(360)/static_cast<float>(1920);
// Controller tunable parameters. //
// P gain.
const int kP = 10;
// D gain.
const int kD = 0;
// Error in encoder pulses correponding to the minimum duty cycle.
const int minErr = 0;
// Error in encoder pulses corresponding to the maximum duty cycle.
const int maxErr = 1024;
// minDutyCycle and maxDutyCycle depend on PWM frequency and can be determined in dc_motor_speed_control . For example for frequency of 25k,
// minDutyCycle = 120 (Motor starts to move),
// maxDutyCycle = 190 (Motor speed reaches its maximum given the supplied voltage).
const int minDutyCycle = 120;
const int maxDutyCycle = 190;
// Controller update rate variables. //
// Difference in time between desired cycle period and its execution time (without any delay()s).
int cycleDiff;
// Control loop start time.
long int startTime;
// Control loop end time.
long int endTime;
// Plotting
float motorPosDeg = 0;
//Plotter p;
void setup() {
Serial.begin(9600);
// Initialize the pins.
pinMode(IN3,OUTPUT);
pinMode(IN4,OUTPUT);
pinMode(ENB,OUTPUT);
pinMode(ENCA,INPUT);
pinMode(ENCB,INPUT);
analogWriteFrequency(ENB, PWM_FREQ);
// Set the initial rotation direction.
setDirection(CCW);
// Start with the motor at rest.
analogWrite(ENB,0);
// Encoder interrupt.
attachInterrupt(digitalPinToInterrupt(ENCA), encoderAISRising, RISING);
attachInterrupt(digitalPinToInterrupt(ENCB), encoderBISRising, RISING);
//p.Begin();
//p.AddTimeGraph("Position v Time", 1000, "Position", motorPosDeg);
}
// *** Encoder interrupt routines. See "Understanding Quadrature Encoded Signals" here: https://www.pjrc.com/teensy/td_libs_Encoder.html" *** //
void encoderAISRising(){
if(digitalRead(ENCB) == HIGH)
motorPos++;
else
motorPos--;
attachInterrupt(digitalPinToInterrupt(ENCA), encoderAISFalling, FALLING);
}
void encoderAISFalling(){
if(digitalRead(ENCB) == LOW)
motorPos++;
else
motorPos--;
attachInterrupt(digitalPinToInterrupt(ENCA), encoderAISRising, RISING);
}
void encoderBISRising(){
if(digitalRead(ENCA) == LOW)
motorPos++;
else
motorPos--;
attachInterrupt(digitalPinToInterrupt(ENCB), encoderBISFalling, FALLING);
}
void encoderBISFalling(){
if(digitalRead(ENCA) == HIGH)
motorPos++;
else
motorPos--;
attachInterrupt(digitalPinToInterrupt(ENCB), encoderBISRising, RISING);
}
// *** ***//
// Default rotation direction is CCW.
void setDirection(bool dir){
// CCW
if (dir == CCW){
digitalWrite(IN3,HIGH);
digitalWrite(IN4,LOW);
}else{
digitalWrite(IN3,LOW);
digitalWrite(IN4,HIGH);
}
}
void loop() {
if (Serial.available() > 0) {
// Read the incoming bytes, until a next line character (Enter) is encountered.
while (1){
incomingByte = Serial.read();
// We have read all the bytes.
if (incomingByte == '\n' || incomingByte == '\r'){
Serial.read();
break;
}else{
// Store the byte in the buffer and move on to the next.
inputBuffer[bufferCnt] = incomingByte;
bufferCnt++;
}
}
// Add a NULL character to the end of the array. Required for using atoi.
inputBuffer[bufferCnt] = '\0';
bufferCnt = 0;
// Convert string to integer.
targetPos = atoi(inputBuffer);
targetPos = targetPos / ratio;
}
// int i = 0;
// if (i % 2 == 0){
// targetPos = 360;
// } else {
// targetPos = 0;
// }
startTime = micros();
// Get the latest motor position.
currentPos = motorPos;
// Position error.
//pError = targetPos - motorPos;
pError = targetPos - currentPos;
// P term of the controller.
pTerm = kP * pError;
dP = currentPos - prevPos;
// D term of the controller. CYCLE_TIME/1000 normalizes the denominator, otherwise dTerm would always be zero (integer division).
dTerm = kD * (dP/(CYCLE_TIME/1000));
contOut = pTerm + dTerm;
// Set the target duty cycle (i.e. speed (i.e. voltage)).
// Error (in terms of encoder pulses) in the range minErr-maxErr is mapped to speed range corresponding to minDutyCycle-maxDutyCycle.
// 4 parameters to tune here.
spd = map(abs(contOut),minErr,maxErr,minDutyCycle,maxDutyCycle);
// Set the direction according to sign of position error (CCW is positive), and then speed.
// One optimization would be calling analogWrite(ENB,abs(spd)) at the start or end of the loop instead
// (at the expense of readibility).
if (pError > 0){
setDirection(CCW);
analogWrite(ENB,abs(spd));
}else if (pError < 0){
setDirection(CW);
analogWrite(ENB,abs(spd));
}
if (pltCounter == PLOT_COUNTER){
float mtrPos = static_cast<float>(motorPos);
motorPosDeg = mtrPos * ratio;
Serial.print(int(motorPosDeg));
Serial.println();
pltCounter = 0;
}
pltCounter++;
prevPos = currentPos;
cycleDiff = micros() - startTime;
// Adjust the update rate.
if (cycleDiff < CYCLE_TIME){
delayMicroseconds(CYCLE_TIME - cycleDiff);
}
//i++;
}
From what i understand of the plot function it utilizes the main arduino connexion to work. Based on how the arduino uart work you can only have 1 com port connexion per com port. This means you can either have the plot or command line open for each uart connexion. It is possible with different version of arduino to have multiple com ports. On the arduino uno there is only one com port "Serial". On the mega i think there are 3 uart ports. If you use a external FTDI UART board you can have the plot window open for serial0 and have the FTDI board connected on Serial1 to have the command line window open. You will have to change your code a little to send commands to serial1.
Here are a couple links to help you.
https://docs.arduino.cc/tutorials/communication/TwoPortReceive
https://docs.arduino.cc/built-in-examples/communication/MultiSerialMega
https://www.amazon.fr/AZDelivery-Adaptateur-FT232RL-s%C3%A9rie-book/dp/B01N9RZK6I?th=1

Convert "String" above 255 to exact "Integer" or "Long" type in Arduino IDE

Appreciate your time.
I am trying to convert "String" read from serial port in serialEvent() of Arduino IDE to integer values with exact representation.
For eg, if String myString = 200 then int myInt should be 200.
I have been somewhat successful but unable to convert String to exact int representation beyond 255.
Solutions I have tried:
1) used .toInt() function in Arduino.
2) used "atoi" and "atol" functions.
3) Serial.parseInt() in loop().
All of these methods start recounting from 0 after every 255 values.
I can't use parseInt since it only works inside loop(). My application requires to store variable value permanently until another value is given through port. For this Arduino Due's flash memory has been used.
The memory storing code seems to work only inside serialEvent().
Code snippet is as below:
#include <stdlib.h>
#include <DueFlashStorage.h>
DueFlashStorage memory;
String x = " ";
int x_size;
int threshold;
void setup(){
Serial.begin(115200);
}
void loop{
Serial.println(memory.read(0));
}
void serialEvent(){
while(Serial.available()){
x = Serial.readStringUntil('\n');
x_size = x.length();
char a[x_size+1];
x.toCharArray(a, x_size+1);
threshold = atoi(a);
memory.write(0, threshold);
}
}
1) Function .toInt() returns LONG and you want INT (I don't know why honestly but it is in documentation)... you need to cast it like this (tried on Arduino ATMEGA and it worked):
#include <stdlib.h>
String x = "1000";
int x_ = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
x_ = (int) x.toInt();
Serial.println(x_);
delay(1000);
}
2) I’m not professional ... but serilEvent() is really old way of doing things and it isn't recommended to use it. You can do it "manually" in the loop() function.
You're only converting 1 character a time, that's why the limit is 255.
If you're not doing anything else, you could stay in a serial.read-loop until all characters are read. For example:
void loop() {
if(Serial.available()) {
byte count = 0;
char number[10]; // determine max size of array
bool wait = true;
while(wait) { // stay in this loop until newline is read
if(Serial.available()) {
number[count] = Serial.read();
if (number[count] == '\n') {
wait = false; // exit while loop
}
count++;
}
}
int threshold = atoi(number);
memory.write(0, threshold);
}
}
For the lack of a good function in Arduino IDE for char/String type to int type conversion (has a limit of 255), I wrote my own conversion code which seems to work perfectly.
int finalcount=0;
void setup(){
Serial.begin(115200);
}
void loop(){
if(Serial.available()) {
int count = 0;
char number[5]; // determine max size of array as 5
bool wait = true;
while(wait) { // stay in this loop until newline is read
if(Serial.available()) {
number[count] = Serial.read();
if (number[count] == '\n') {
finalcount = count; //max array size for integer; could be less than 5
wait = false; // exit while loop
}
count++;
}
}
int val[finalcount]; //array size determined for integer
int placeValue;
int finalval[finalcount];
int temp=0;
int threshold;
for(int i = 0; i<finalcount; i++){
val[i] = (int)number[i]-48; //convert each char to integer separately
placeValue = pow(10,(finalcount-1)-i); //calculate place value with a base of 10
finalval[i] = val[i]*placeValue; //update integers with their place value
temp += finalval[i] ; //add all integers
threshold = temp; //resulting number stored as threshold
}
Serial.println(threshold); //prints beyond 255 successfully !
}
}
I solved the problem using highByte and lowByte functions of Arduino. Works flawlessly.
#include <DueFlashStorage.h>
DueFlashStorage m;
byte a1,a2;
int val;
void setup() {
Serial.begin(115200); //start the serial communication
}
void loop()
{
if (Serial.available()>0)
{
val = Serial.parseInt(); //read the integer value from series
if(val>0){
a1 = highByte(val); //get the higher order or leftmost byte
a2 = lowByte(val); //get the lower order or rightmost byte
m.write(0,a1); //save in arduino due flash memory address 0
m.write(1,a2); //save in arduino due flash memory address 1
}
int myInteger;
myInteger = (m.read(0)*256)+m.read(1); //convert into the true integer value
Serial.println(myInteger);
}

Arduino: How to read SD card text file line by line

I'm working on a project to read "current" in "data.txt" from SDcard. Goal is to read it line by line and input to my int "TargetCur".
Code structure:
1. Open "data.txt" from SDcard
2. Read first line data
3. Input read data into int "TargetCur"
4. Arduino perform action
5. Once action above completed, read second line data from "data.txt"
6. Repeat step 3 to 5 above
"data.txt" look like this:
Current
2.179
3.179
2.659
2.859
#include <SPI.h>
#include <SD.h>
File Data;
// Declare the pins used:
int TargetCur = 0;
void setup() {
Serial.begin(9600); // initialize serial communications at 9600 bps:
TCCR1B = TCCR1B & B11111000 | B00000001;
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
myFile = SD.open ("data.txt", FILE_WRITE);
}
void loop() {
TargetCur = Serial.write(myFile.read());
//perform action//
}
To read line by line, create a String readLine () function that reads char by char until the end of the line.
You can always create your own function that suits you needs. If you know the max line size than a statically declared char[] will work best like so.
int index = 0;
char stringArray[MAX_LINE_LEN];
while ((int next = myFile.read()) != -1)
{
char nextChar = (char) next;
if (nextChar == '\n')
{
stringArray[index] = '\0';
/* Do something with char array */
index = 0;
}
else
{
stringArray[index] = nextChar;
index += 1;
}
}
If you don't know the max line size that this becomes a bit harder and you need to use malloc() to dynamically grow the size of the buffer.

Serial Communication Arduino C++ Builder

I am trying to write an Application with C++ Builder which should display for example the temperature and humidity of a measurement from the Arduino. Arduino and my Application are connected via TComPort. I am using a Baudrate of 115200.
VCL Code:
AnsiString temp, hum;
ComPort1->ReadStr(temp, 5);
RichEdit1->Text = temp;
ComPort1->ReadStr(hum, 5);
RichEdit3->Text = hum;
Arduino Code:
DHT dht(10, DHT11);
void setup(void)
{
dht.begin();
Serial.begin(115200);
}
void loop(void)
{
// Measure the humidity & temperature
float h = dht.readHumidity();
float t = dht.readTemperature();
// Transform to String
String temp = String( t );
String hum = String( h );
delay(10);
Serial.print(temp);
Serial.print(hum);
delay(5000);
}
When I start my Application (VCL with C++ Builder) and connect to the Arduino every 5 seconds, the values should be refreshed by new values. Most of the time the 2 values are correct and it displays like it should for example
Temperature: 24:00
Humidity: 55.00
But for some reason every now and then it displays something like this:
Temperature: .0051
Humditiy: .00
or some other weird values. 5 seconds later the values are correct again. This happens every 20-30 seconds 1 time and i dont know why this keeps happening.
In the TComPort.OnRxChar event, there is a Count parameter that tells you how many bytes are available. Don't read more than that. Buffer the bytes to the side, and then read only complete values from that buffer.
For example:
private:
AnsiString ComPortBuffer;
void __fastcall TMyForm::ComPort1RXChar(TObject *Sender, int Count)
{
AnsiString s;
int len = ComPort1->ReadStr(s, 5);
if (len > 0)
{
if (len < 5)
s.SetLength(len);
ComPortBuffer += s;
}
while (ComPortBuffer.Length() >= 10)
{
AnsiString temp = ComPortBuffer.SubString(1, 5);
AnsiString hum = ComPortBuffer.SubString(6, 5);
ComPortBuffer.Delete(1, 10);
// use temp and hum as needed...
RichEdit1->Text = temp;
RichEdit3->Text = hum;
}
}

Arduino SD card fails to write when used with another SPI device

I have an ADXL355 accelerometer attached to an Adafruit Feather Adalogger. I can configure and read the sensor. I can also write binary values to the SD card. The problem occurs when I try to read from the sensor and then write that data to the SD card. The only thing I can think of is I'm somehow messing up the SPI communication but I can't see where. I looked through pins_arduino.h for my board and the SD Card (pin 4) is on a different register than pin 10 so I don't see how I'm breaking things.
My operations proceed like this. Global sensor creation, Serial.begin, SD.begin, SPI.begin, Test sensor connection, Create file for output on SD card, Initialize sensor, Read sensor FIFO, Write to file, repeat last 2 forever.
The file is created but remains at 0 file size, ie nothing is actually written to the card.
The sensor can operate at 4 kHz which was hard to achieve using the digitalWrite functions so I switched to using the port registers on the Feather. I do it like this:
#include <SM_ADXL355_SPI_fast.h>
#include <SPI.h>
#include <SD.h>
#define cardSelect 4
ADXL355_SPIF adxl355(&DDRB, &PORTB, _BV(6)); // values taken from pins_arduino.h, tested and working on pin 10
void setup() {
Serial.begin(57600);
while(!Serial){
// wait for Serial
}
SD.begin(cardSelect);
SPI.begin();
while(!adxl355.TestConnection()){
delay(1000);
}
adxl355.OpenFile("TestSPI.bin");
adxl355.Initialize(1, 10, 0); // set range to 2g's, frequency to 4 Hz and filter to off
}
void loop() {
while(true){ // avoid Arduino overhead of their loop function
adxl355.ReadFIFO();
adxl355.WriteFIFOToFile();
}
}
Here is the ADXL constructor
ADXL355_SPIF::ADXL355_SPIF(volatile uint8_t * outReg, volatile uint8_t * outPort, uint8_t bitValue) : sensorOutReg(outReg), sensorPort(outPort), sensorBitValue(bitValue){
*sensorOutReg |= sensorBitValue;
*sensorPort |= sensorBitValue;
sensorWriteCount = 0;
}
TestConnection tests that the DeviceID reads 0xAD. Initialize sets the G range, sample rate in Hz and filter. I have tested these with serial output and they work properly.
OpenFile looks like this:
bool ADXL355_SPIF::OpenFile(const String& fileName){
sensorFile = SD.open(fileName, FILE_WRITE);
if (!sensorFile){
Serial.print("Could not create file: ");
Serial.println(fileName);
return false;
}
return true;
}
After running this a file does get created on the SD card called "TESTSPI.BIN" with 0 file size.
ReadFIFO reads the numbers of entries in FIFO, stored as fifoCount and then populates a buffer (sensorFIFO[32][3]) with the values from the FIFO. I've printed this buffer to Serial to show that it's working. Here is that function
void ADXL355_SPIF::ReadFIFO(){
ReadRegister(ADXL355_RA_FIFO_ENTRIES, 1);
fifoCount = buffer[0];
ReadFIFOInternal();
return;
}
void ADXL355_SPIF::ReadFIFOInternal(){
SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE0));
*sensorPort &= ~sensorBitValue;
uint8_t spiCommand = ADXL355_RA_FIFO_DATA << 1 | ADXL355_READ;
SPI.transfer(spiCommand);
int i = 0;
unsigned long tempV;
unsigned long value;
while(i < fifoCount){
for (int ptr = 0; ptr < 3; ++ptr){
buffer[0] = SPI.transfer(0x0);
value = buffer[0];
value <<= 12;
tempV = SPI.transfer(0x0);
tempV <<= 4;
value |= tempV;
tempV = SPI.transfer(0x0);
tempV >>=4;
value |= tempV;
if (buffer[0] & 0x80) {
value |= 0xFFF00000;
}
long lValue = static_cast<long>(value);
sensorFIFO[i][ptr] = scaleFactor * lValue;
}
i += 3;
}
SPI.endTransaction();
*sensorPort |= sensorBitValue;
return;
}
Here is WriteFIFOToFile:
void ADXL355_SPIF::WriteFIFOToFile(){
if (fifoCount > 0){
sensorFile.write(reinterpret_cast<const char *>(&sensorFIFO), 4 * fifoCount);
}
sensorWriteCount += fifoCount;
if (sensorWriteCount >= 100){
sensorFile.flush();
sensorWriteCount = 0;
}
}
After allowing this to run for a while the file size is always 0. I tried a simple binary write function just to test the card. It looks like this and it worked.
#include <SD.h>
#define cardSelectPin 4
const float pi=3.14159;
File oFile;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
while(!Serial){
// wait for serial
}
SD.begin(cardSelectPin);
oFile = SD.open("Test.bin", FILE_WRITE);
Serial.println(sizeof(int));
Serial.println(sizeof(float));
float testFloat[32][3];
for (int i = 0; i < 32; ++i){
for (int j = 0; j < 3; ++j){
testFloat[i][j] = pi * (i + 1) + j;
}
}
oFile.write(reinterpret_cast<const char *>(&testFloat), sizeof(float) * 96);
oFile.close();
Serial.println("Finished writing file.");
}
void loop() {
// put your main code here, to run repeatedly:
}
The problem was that flush was not being called correctly. I had created a buffer to hold data from the FIFO and it would flush the card when it would get full enough such that a subsequent read would overflow. At that time it would call flush. This is what was intended with the variable sensorWriteCount. This variable was of type uint8_t when it should have been a uint16_t.
Changing to the correct type fixed the problem. I would have deleted this question because it boils down to a typo, but once an answer has been posted the system doesn't allow that.
The only difference between the not-working sketch and the working one is the closing of the sd card. The sd card MUST be closed, I had the same problem you have and I assume that the file gets its boundaries written in its filesystem at file close call.
To solve your issue, use a push button. When you push it, it will close the file and stop reading/processing sensors. You can also use this button to start reading and recording sensors data on sd card again (toggle).

Resources