Tachometer+Load cell + SD card + LCD ??

Ok so I have a complicated system I am putting together , it is a DYNO for testing horse Power on small gas engines , all of this is trying to run on a Nano board with SD card and LCD display , I have not had memory issues so far BUT I have timing issues I believe , I am not experienced enough to fix this so far.

  1. main issue : my RPM's are Off bad , I know this has to do with timing and trying to do to many things in the loop , I have tried everything I can think of.
  2. Is there a solution to this ? If not see #3
  3. Is it possible to do the RPM data on one NANO #1 and everything else on the other one #2 ( WIth SD card and LCD ) and send the data to the SD card on #2 , BUT If I can have the data on the SD record data from both boards in a CSV file with 2 columns ?? I need to import this data into a chart program.
  4. I am using A. NANO , B. LCD 1602 I2C , C. SD card board , D. HX711 load cell board , E. Hall Sensor pin 5

This is the RPM code it seems to work just fine by itself

// digital pin 2 is the hall pin
int hall_pin = 5;
// set number of hall trips for RPM reading (higher improves accuracy)
float hall_thresh = 5.0;

void setup() {
  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
  // make the hall pin an input:
  pinMode(hall_pin, INPUT);
}

// the loop routine runs over and over again forever:
void loop() {
  // preallocate values for tach
  float hall_count = 1.0;
  float start = micros();
  bool on_state = false;
  // counting number of times the hall sensor is tripped
  // but without double counting during the same trip
  while(true){
    if (digitalRead(hall_pin)==0){
      if (on_state==false){
        on_state = true;
        hall_count+=1.0;
      }
    } else{
      on_state = false;
    }
    
    if (hall_count>=hall_thresh){
      break;
    }
  }
  
  // print information about Time and RPM
  float end_time = micros();
  float time_passed = ((end_time-start)/1000000.0);
 // Serial.print("Time Passed: ");
  Serial.print(time_passed);
 Serial.println("s");
  float rpm_val = (hall_count/time_passed)*60.0;
  Serial.print(rpm_val);
  Serial.println(" RPM");
  delay(1);        // delay in between reads for stability
}

This is the main code ( it has the RPM code in it ) the RPM's are all messed up , they are all over the place timing wise) , is there a way to sort this out ?? )

/*
  SD card read/write

  This example shows how to read and write data to and from an SD card file
  The circuit:
  SD card attached to SPI bus as follows:
** MOSI - pin 11
** MISO - pin 12
** CLK - pin 13
** CS - pin 4 (for MKRZero SD: SDCARD_SS_PIN)
*/

// digital pin 5 is the hall pin,
//BRN= +V, Blu=gnd, Blk= load
int hall_pin = 5;
// set number of hall trips for RPM reading (higher improves accuracy)
float hall_thresh = 6; // 10 ?, 5

#include "HX711.h"

#include <Wire.h> // Library for I2C communication
#include <LiquidCrystal_I2C.h> // Library for LCD
// Wiring: SDA pin is connected to A4 and SCL pin to A5.
// Connect to LCD via I2C, default address 0x27 (A0-A2 not jumpered)
LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x27, 16, 2); // Change to (0x27,20,4) for 20x4 LCD.

#define calibration_factor -9300.0 //This value is obtained using the SparkFun_HX711_Calibration sketch

#define LOADCELL_DOUT_PIN  3
#define LOADCELL_SCK_PIN  2

HX711 scale;
#include <SPI.h>
#include <SD.h>
// Base name must be six characters or less for short file names.
#define FILE_BASE_NAME "Data"
File file;
const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1;
char fileName[] = FILE_BASE_NAME "00.csv";
const int chipSelect = 4;

void setup() {
  lcd.begin();
  lcd.backlight();
  Serial.begin(115200);

  if (!SD.begin(chipSelect)) {
    Serial.println(F("begin failed"));
    return;
  }
  while (SD.exists(fileName)) {
    if (fileName[BASE_NAME_SIZE + 1] != '9') {
      fileName[BASE_NAME_SIZE + 1]++;
    } else if (fileName[BASE_NAME_SIZE] != '9') {
      fileName[BASE_NAME_SIZE + 1] = '0';
      fileName[BASE_NAME_SIZE]++;
    } else {
      Serial.println(F("Can't create file name"));
      return;
    }
  }
  // make the hall pin an input:
  pinMode(hall_pin, INPUT);

  //Serial.println("HX711 scale demo");

  scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
  scale.set_scale(calibration_factor); //This value is obtained by using the SparkFun_HX711_Calibration sketch
  scale.tare(); //Assuming there is no weight on the scale at start up, reset the scale to 0

  //Serial.println("Readings:");
}

void loop() {

  float hall_count = 1.0;
  float start = micros();
  bool on_state = false;
  // counting number of times the hall sensor is tripped
  // but without double counting during the same trip
  while (true) {
    if (digitalRead(hall_pin) == 0) {
      if (on_state == false) {
        on_state = true;
        hall_count += 1.0;
      }
    } else {
      on_state = false;
    }

    if (hall_count >= hall_thresh) {
      break;
    }
  }

  // print information about Time and RPM
  float end_time = micros();
  float time_passed = ((end_time - start) / 1000000.0);
  // Serial.print("Time Passed: ");
  // Serial.print(time_passed);
  // Serial.println("s");
  float rpm_val = (hall_count / time_passed) * 60.0;
  Serial.print(rpm_val - 60);
  Serial.println(" RPM");
  // delay(50);
  Serial.println(" ' ");
  Serial.print(scale.get_units(2), 1); //scale.get_units() returns a float
  Serial.print(" OZ "); //You can change this to kg but you'll need to refactor the calibration_factor
  Serial.println();

  lcd.setCursor(0, 1);
  lcd.print("BuzzDyno");
  lcd.setCursor(0, 0);
  lcd.print("RPM  =  "); lcd.print(rpm_val - 60);
  lcd.setCursor(0, 1);
  lcd.print("Load  =  "); lcd.print(scale.get_units(2), 1);


  file = SD.open(fileName, FILE_WRITE);
  if (!file) {
    Serial.println(F("open failed"));
    return;
  }
  file.print(rpm_val - 60);
  file.print(scale.get_units(2), 1);

  file.close();
  delay(10);
}

IF I am missing anything let me know and THANKS !

This is the main code ( it has the RPM code in it ) the RPM's are all messed up , they are all over the place timing wise) , is there a way to sort this out ?? )

What does "all messed up" mean. You are grabbing start and stop times on each side of a blocking routine to count pulses. How do these times change from the simple hall only program?

If you leave out either the scale or the SD card, are the results like the simple program? The HX711 library disables interrupts for the scale reading, and can effect system timing.

You read the scale 3 different times(with 2 readings each time) for Serial, lcd, and SD. Read it once and use the same value in all cases.

Why are hall count and hall threshold floats? They are certainly limited to integer values.

cattledog:
What does "all messed up" mean. You are grabbing start and stop times on each side of a blocking routine to count pulses. How do these times change from the simple hall only program?

If you leave out either the scale or the SD card, are the results like the simple program? The HX711 library disables interrupts for the scale reading, and can effect system timing.

You read the scale 3 different times(with 2 readings each time) for Serial, lcd, and SD. Read it once and use the same value in all cases.

Why are hall count and hall threshold floats? They are certainly limited to integer values.

so its obvious I dont really understand coding , You are actually the person I wanted to talk to , I tried your Tach/RPM code and it worked great ! I tried to integrate it into this code but it has the same problem, TIMING

  1. Meessed up means the rpm's will be 800 then 1204 then 960 so unstable and inaccurate. I know the loop just runs I just dont know how to do all these things in the LOOP
  2. no it is still "bad " RPM;s
  3. Scale I need to know how to do that or figure it out , get the number 1 time and distribute it
  4. I did not write that RPM code , I found it on here , now that I know I can fix it .
    I would prefer using your code IF I can get it to work accurately , Or use 2 NANO boards and send the RPM value to the main board/SD /LCD

The best rpm code usually uses interrupts, but that is an issue with the Hx711 code which disables interrupts for the scale reading.

The rpm code you are using is blocking(that is, nothing else is running when it is in the while loop before the break after 6 counts) so it should not really be affected by the rest of the program.

What are the "stable and accurate" rpm values when the rpm code is running by itself?

cattledog:
The best rpm code usually uses interrupts, but that is an issue with the Hx711 code which disables interrupts for the scale reading.

The rpm code you are using is blocking(that is, nothing else is running when it is in the while loop before the break after 6 counts) so it should not really be affected by the rest of the program.

What are the "stable and accurate" rpm values when the rpm code is running by itself?

So thats basically the conclusion I came to but I was not sure , My stable RPM's when running by itself seem to be accurate , they are stable but as far as accurate I am not 100% sure , I have a 2nd RPM meter off of Amazon with a separate sensor I use as a reference , as far as it accuracy I can only go off the stated Spec's . I can calibrate the results in the output.
So I need to use a 2nd Nano to get the RPM's , got any ref. to send the Serial to the main Board with the SD card , and put the RPM into the SD card in column #1 ? my load is setup for column #2 ?? , I dont want to have to use 2 sd cards if possible ,
it looks like a can share the SPI bus just have to use separate CS lines from each Nano ?? does this make sense to you ? Multiple SPI Devices in Arduino Uno - Programming Questions - Arduino Forum

And share the LCD 2 arduino's and 1 LCD sharing I2C - Networking, Protocols, and Devices - Arduino Forum

Ok so I got your code and added serial 0/1 pins to it to send the rpm data to the other Nano with the SD card and display BUT I am not sure how to add the data to the serial port string ? I get the rpm on the serial mon but it has extra characters I cant seem to get rid of , I am new to serial so

volatile byte  count = 0;
volatile boolean validA = false;//control variable to ensure B follows A

volatile unsigned long startTimeA;
volatile unsigned long endTimeA;
unsigned long copy_startTimeA;
unsigned long copy_endTimeA;

volatile unsigned long startTimeB;
volatile unsigned long endTimeB;
unsigned long copy_startTimeB;
unsigned long copy_endTimeB;

volatile boolean finishPulseA = false;
volatile boolean finishPulseB = false;

float periodA;
float periodB;

unsigned int rpmA = 0;
unsigned int rpmB = 0;

#include <LiquidCrystal_I2C.h> // Library for LCD
// Wiring: SDA pin is connected to A4 and SCL pin to A5.
// Connect to LCD via I2C, default address 0x27 (A0-A2 not jumpered)
LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x27, 16, 2); // Change to (0x27,20,4) for 20x4 LCD.

//char mystr[5] = "Hello , "; //String data

const int chipSelect = 4;
void setup()
{
  Serial.begin(115200);
  Serial.println("starting......");

  lcd.begin();
  lcd.backlight();


  pinMode(3, INPUT_PULLUP);//interrupt pin
  attachInterrupt(digitalPinToInterrupt(3), isrCount, FALLING);//interrupt on pin3

}

void loop()
{
  if (finishPulseA == true )
  {
    // disable interrupts, make protected copy of time values
    noInterrupts();
    finishPulseA = false;//reset flag
    copy_startTimeA = startTimeA;
    copy_endTimeA = endTimeA;
    interrupts();

    periodA = (copy_endTimeA - copy_startTimeA);
    rpmA = 60000000UL / periodA; //one pulse per revolution
    lcd.setCursor(0, 0);
    lcd.print("RPM = "); lcd.println(rpmA);
    // Serial.print("RPMA = ");
    Serial.println(rpmA);
    Serial.write(rpmA);
  }

  if (finishPulseB == true)
  {
    // disable interrupts, make protected copy of time values
    noInterrupts();
    finishPulseB = false;//reset flags
    copy_startTimeB = startTimeB;
    copy_endTimeB = endTimeB;
    interrupts();

    periodB = (copy_endTimeB - copy_startTimeB);
    rpmB = 60000000UL / periodB; //one pulse per revolution
    //Serial.print("RPMB = ");
   // Serial.println(rpmB);
  }
}

void isrCount()
{
  if (count == 0)//first entry to isr
  {
    startTimeA = micros();
    endTimeB = micros();
    if (validA)  //ensures initial pulse is A, not B without start time
      finishPulseB = true;
    count = 1;
  }

  else if (count == 1)//second entry to isr
  {
    endTimeA = micros();
    startTimeB = micros();
    finishPulseA = true;
    validA = true;
    count = 0;
  }
  
}

I think you are headed in the wrong direction.

  1. Using two Nanos will create other issues of synchronization and you have not fully explored solutions with a single arduino. Indeed I now think that there were significant problems with your original blocking pulse reading routine with lack of synchronization to a leading edge.

  2. I'm not certain what issue I was trying to solve with the interrupt code you presented, but it is not good for your application. You will be better counting several pulses to get an average over a period of time,

  3. I would like to try a switch case (state machine) approach to the three operations readHall, readScale, writeSD. Interrupts can be turned on for the hall sensor readings and off during the scale reading. You will cycle through the three states very quickly. I think the association of a scale reading with an rpm will be as close as you will get in a two processor situation.

  4. I am working on a version of code which has the actual scale reading and SD writing , but for now, here is some dummy code which shows the approach. Jumper pin 11 to pin2 for a test pulse.

  5. Because of the interrupts on pin 2, the scale will need to be on different pins, but the library supports the use of any pin. Are there two free pins other than 2 and 3 which you prefer to use?

#define readHall 1
#define readScale 2
#define writeSD 3

byte hallPin = 2;
byte state = readHall;//readScale;//writeSD;

//rpm/interrupt variables
byte numCount = 5; //rising edge counts 
volatile byte count = 0;
volatile unsigned long startTime;
volatile unsigned long endTime;
volatile boolean finishCount = false;
unsigned long period;
unsigned int rpm;

void isrCount()
{
  if (count == 0)//first entry to isr
  {
    startTime = micros();
  }
  if (count == numCount)
  {
    endTime = micros();
    detachInterrupt(digitalPinToInterrupt(hallPin));
    finishCount = true;
  }
  count++; //increment after test for numCount
}

void setup() {
  Serial.begin(115200);
  //test output jumper pin 11 to pin 2
  pinMode(11, OUTPUT);
  tone(11,50);//50Hz square wave 20ms period 50*60 = 3000 pulses/min
  //tone(11,40);//40Hz square wave 25ms period 40*60 = 2400 pulses/min
  attachInterrupt(digitalPinToInterrupt(hallPin), isrCount, RISING);//interrupt on pin 2
}

void loop() {
  switch (state)
  {
    case readHall:
      if (finishCount == true)
      {
        finishCount = false;//reset flag
        count = 0;
        period = (endTime - startTime);// micros
        //Serial.println(period);
        period = period / numCount; //5 intervals in measurement
        rpm =  60 * (1000000ul / period);
        Serial.print("RPM = ");
        Serial.println(rpm);
        state = readScale;
      }
      break;
      
    case readScale:
      Serial.println("read scale");
      delay(50);///emulate time in case
      state = writeSD;
      break;

    case writeSD:
      Serial.println("write SD");
      delay(50);//emulate time in case
      EIFR = bit (INTF0);//clear interrupt flag
      attachInterrupt(digitalPinToInterrupt(hallPin), isrCount, RISING);//interrupt on pin3
      state = readHall;
      break;
  }
}

Here is the switch case program with the scale and SD. The scale pins are changed to 6 and 7. I'm not clear about the lcd library you actually have and the init() or begin()?

EDIT: Change tone() to pin 3 avoid spi conflict on pin 11

This compiles, but I don't have the hardware to test with.

#include "HX711.h"
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x27, 16, 2);

#define calibration_factor -9300.0 //This value is obtained using the SparkFun_HX711_Calibration sketch
#define LOADCELL_DOUT_PIN  6
#define LOADCELL_SCK_PIN   7
HX711 scale;
float scaleReading;

#include <SPI.h>
#include <SD.h>
#define FILE_BASE_NAME "Data"
File file;
const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1;
char fileName[] = FILE_BASE_NAME "00.csv";
const int chipSelect = 4;

//rpm/interrupt variables
byte hallPin = 2;
byte numCount = 5; //rising edge count give numCount intervals to measure
volatile byte count;
volatile unsigned long startTime;
volatile unsigned long endTime;
volatile boolean finishCount = false;
unsigned long period;
unsigned int rpm;

//switch state indexes
#define readHall 1
#define readScale 2
#define writeSD 3
byte state = readHall;

void isrCount()
{
  if (count == 0)//first entry to isr
  {
    startTime = micros();
  }
  if (count == numCount)
  {
    endTime = micros();
    detachInterrupt(digitalPinToInterrupt(hallPin));
    finishCount = true;
  }
  count++; //increment after test for numCount
}

void setup() {
  //lcd.begin();//not certain of right command for your library?
  lcd.init();
  lcd.backlight();
  //lcd print unchanging aspects of display
  lcd.setCursor(0, 0);
  lcd.print("RPM = ");//print values at cursor 6,0
  lcd.setCursor(0, 1);
  lcd.print("Load = ");//print values at cursor 7,1

  Serial.begin(115200);
  //test output jumper pin 3 to pin 2
  pinMode(3, OUTPUT);
  tone(3, 50); //50Hz square wave 20ms period 50*60 = 3000 pulses/min
  //tone(3,40);//40Hz square wave 25ms per

  if (!SD.begin(chipSelect)) {
    Serial.println(F("begin failed"));
    return;
  }
  while (SD.exists(fileName)) {
    if (fileName[BASE_NAME_SIZE + 1] != '9') {
      fileName[BASE_NAME_SIZE + 1]++;
    } else if (fileName[BASE_NAME_SIZE] != '9') {
      fileName[BASE_NAME_SIZE + 1] = '0';
      fileName[BASE_NAME_SIZE]++;
    } else {
      Serial.println(F("Can't create file name"));
      return;
    }
  }
  //make the hall pin an input:
  pinMode(hallPin, INPUT);//maybe should be input pullup?

  scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
  scale.set_scale(calibration_factor); //This value is obtained by using the SparkFun_HX711_Calibration sketch
  scale.tare(); //Assuming there is no weight on the scale at start up, reset the scale to 0

  //test signal
  pinMode(11, OUTPUT);
  tone(11, 50); //50Hz square wave 20ms period 50*60 = 3000 pulses/min
  //tone(11,40);//40Hz square wave 25ms period 40*60 = 2400 pulses/min
  attachInterrupt(digitalPinToInterrupt(hallPin), isrCount, RISING);//interrupt on pin 2
}

void loop() {
  switch (state) {
    case readHall:
      if (finishCount == true)
      {
        finishCount = false;//reset flag
        count = 0;
        period = (endTime - startTime);// micros
        //Serial.println(period);
        period = period / numCount; //5 intervals in measurement
        rpm =  60 * (1000000ul / period);
        Serial.print("RPM = ");
        Serial.println(rpm);
        //print lcd values at cursor 6,0
        lcd.setCursor(6, 0);
        //clear previous value
        lcd.print("         ");//9 spaces
        lcd.setCursor(6, 0);
        lcd.print(rpm);
        state = readScale;
      }
      break;

    case readScale:
      Serial.println("read scale");
      scaleReading = scale.get_units(2);//two readings averaged
      Serial.print("Scale Reading OZ = ");//You can change this to kg but you'll need to refactor the calibration_factor
      Serial.println(scaleReading, 1);
      //print values at cursor 7,1
      lcd.setCursor(7, 1);
      //clear previous value
      lcd.print("        ");//8 spaces
      lcd.setCursor(7, 1);
      lcd.print(scaleReading, 1);
      state = writeSD;
      break;

    case writeSD:
      Serial.println("write SD");
      file = SD.open(fileName, FILE_WRITE);
      if (!file) {
        Serial.println(F("open failed"));
        return;
      }
      file.print(rpm);
      file.print(scaleReading, 1);
      file.close();
      EIFR = bit (INTF0);//clear interrupt flag
      attachInterrupt(digitalPinToInterrupt(hallPin), isrCount, RISING);//interrupt on pin 2
      state = readHall;
      break;
  }
}

THANKS I will try this today , I had to change to this lcd.begin(); ( I guess there is an older library that this function changed ? ) It compiled and loaded , 6-7 pins is fine .
I will do a test this afternoon , I appreciate your help , this is beyond what I can do big time !
As I was getting into the 2 Nano deal I realized that sync and several other things would be an issue

I see how you are doing that ! very nice this is stuff I need to know . So Break stops the routines from over lapping I assume ?
I see the Avg. of the hall sensor is - numCount correct ?

I see the Avg. of the hall sensor is - numCount correct ?

Yes. There was an error in the comments which was wrong and I corrected it. The isr is designed to read 5 full pulses from start to finish.

I will do a test this afternoon

Fingers crossed.

See EDIT in previous post about moving the tone() test from pin 11 to pin 3 to avoid conflict with SPI mosi.

cattledog:
Yes. There was an error in the comments which was wrong and I corrected it. The isr is designed to read 5 full pulses from start to finish.

Fingers crossed.

See EDIT in previous post about moving the tone() test from pin 11 to pin 3 to avoid conflict with SPI mosi.

well IT WORKS !! so far so good , I had to make a few changes , Pin 11 for the TEST freq. was changed to pin 10 ( pin 11 was for the SD card MOSI ) , also the SD write needed line 153 file.print(","); for the CSV file separate column , When I first ran it I had the power to the Load cell messed up ! spent 30 min., until I realized I had 2 gnd's and no Vcc LOL !

so far I tested it with a hall sensor and the test freq. and all good , it create the new file names on the SD card and the RPM seem stable , I will put it on my system with the ref. RPM counter and see how they match up ,

any idea on the MAX RPM on this setup ? In the future I may have a direct off of the crank shaft DYNO setup and these small motors can go above 20,000 Rpm ! right now Im at about 4,000 max .

I want to thank you ! VERY MUCH this is a huge forward leap for me ! , If you ever need any 3d modeling or printing let me know Or Cad design , I OWE YOU !

/*
  SD card read/write

  This example shows how to read and write data to and from an SD card file
  The circuit:
  SD card attached to SPI bus as follows:
** MOSI - pin 11
** MISO - pin 12
** CLK - pin 13
** CS - pin 4 (for MKRZero SD: SDCARD_SS_PIN)
*/
#include "HX711.h"
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x27, 16, 2);

#define calibration_factor -9300.0 //This value is obtained using the SparkFun_HX711_Calibration sketch
#define LOADCELL_DOUT_PIN  6
#define LOADCELL_SCK_PIN   7
HX711 scale;
float scaleReading;

#include <SPI.h>
#include <SD.h>
#define FILE_BASE_NAME "Data"
File file;
const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1;
char fileName[] = FILE_BASE_NAME "00.csv";
const int chipSelect = 4;

//rpm/interrupt variables
byte hallPin = 2;
byte numCount = 5; //rising edge count give numCount - 1 intervals to measure
volatile byte count;
volatile unsigned long startTime;
volatile unsigned long endTime;
volatile boolean finishCount = false;
unsigned long period;
unsigned int rpm;

//switch state indexes
#define readHall 1
#define readScale 2
#define writeSD 3
byte state = readHall;

void isrCount()
{
  if (count == 0)//first entry to isr
  {
    startTime = micros();
  }
  if (count == numCount)
  {
    endTime = micros();
    detachInterrupt(digitalPinToInterrupt(hallPin));
    finishCount = true;
  }
  count++; //increment after test for numCount
}

void setup() {
  //lcd.begin();//not certain of right command for your library?
  lcd.begin();
  lcd.backlight();
  //lcd print unchanging aspects of display
  lcd.setCursor(0, 0);
  lcd.print("RPM = ");//print values at cursor 6,0
  lcd.setCursor(0, 1);
  lcd.print("Load = ");//print values at cursor 7,1

  Serial.begin(115200);
  //test output jumper pin 11 to pin 2
  pinMode(10, OUTPUT);
  tone(10, 50); //50Hz square wave 20ms period 50*60 = 3000 pulses/min
  //tone(10,40);//40Hz square wave 25ms per

  if (!SD.begin(chipSelect)) {
    Serial.println(F("begin failed"));
    return;
  }
  while (SD.exists(fileName)) {
    if (fileName[BASE_NAME_SIZE + 1] != '9') {
      fileName[BASE_NAME_SIZE + 1]++;
    } else if (fileName[BASE_NAME_SIZE] != '9') {
      fileName[BASE_NAME_SIZE + 1] = '0';
      fileName[BASE_NAME_SIZE]++;
    } else {
      Serial.println(F("Can't create file name"));
      return;
    }
  }
  //make the hall pin an input:
  pinMode(hallPin, INPUT);//maybe should be input pullup?

  scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
  scale.set_scale(calibration_factor); //This value is obtained by using the SparkFun_HX711_Calibration sketch
  scale.tare(); //Assuming there is no weight on the scale at start up, reset the scale to 0

  //test signal
  pinMode(10, OUTPUT);
  tone(10, 50); //50Hz square wave 20ms period 50*60 = 3000 pulses/min
  //tone(10,40);//40Hz square wave 25ms period 40*60 = 2400 pulses/min
  attachInterrupt(digitalPinToInterrupt(hallPin), isrCount, RISING);//interrupt on pin 2
}

void loop() {
  switch (state) {
    case readHall:
      if (finishCount == true)
      {
        finishCount = false;//reset flag
        count = 0;
        period = (endTime - startTime);// micros
        //Serial.println(period);
        period = period / numCount; //5 intervals in measurement
        rpm =  60 * (1000000ul / period);
        Serial.print("RPM = ");
        Serial.println(rpm);
      // print lcd values at cursor 6,0
       lcd.setCursor(6, 0);
       // clear previous value
        lcd.print("         ");//9 spaces
        lcd.setCursor(6, 0);
        lcd.print(rpm);
        state = readScale;
      }
      break;

    case readScale:
     // Serial.println("read scale");
      scaleReading = scale.get_units(2);//two readings averaged
      Serial.print("Scale Reading OZ = ");//You can change this to kg but you'll need to refactor the calibration_factor
      Serial.println(scaleReading, 1);
      //print values at cursor 7,1
     // lcd.setCursor(7, 1);
      //clear previous value
     // lcd.print("        ");//8 spaces
      lcd.setCursor(7, 1);
      lcd.print(scaleReading, 1);
      state = writeSD;
      break;

    case writeSD:
      // Serial.println("write SD");
      file = SD.open(fileName, FILE_WRITE);
      if (!file) {
        Serial.println(F("open failed"));
        return;
      }
      file.print(rpm);
      file.print(",");
      file.println(scaleReading, 1);
      file.close();
      
      EIFR = bit (INTF0);//clear interrupt flag
      attachInterrupt(digitalPinToInterrupt(hallPin), isrCount, RISING);//interrupt on pin 2
      state = readHall;
      break;
  }
}

well IT WORKS !! so far so good

Great news. Have you run the scale and the hall sensor reads together?

any idea on the MAX RPM on this setup ? In the future I may have a direct off of the crank shaft DYNO setup and these small motors can go above 20,000 Rpm ! right now I'm at about 4,000 max .

You can certainly test your system with tone(pin,1000) = 60,000 rpm but I don't know how realistic that is for your signals and the rise time of the hall sensor. I would expect that you could read 20,000 rpm. I think you bigger problem will be fast accelerations and throttle "goosing" in neutral. Depending on the scale reading and sd functions, you may not wind up reading rpm at faster than 10 times/second. I'm not certain how long the scale readings actually take as well. The rpm and the scale will be slightly out of sync, but time will tell if that is a major issue.

The SD writing can take a bit of time when the library's storage buffer is physically written to the card.

There's lots to read about Arduino dynamometers on Google and the website search. If you get very serious you may wind up on a processer with multiple cores to handle the rpm and load in a truly synchronous instead of high speed sequential fashion. You may find that spi FRAM, a solid state memory, is faster than the SD card.

Keep us posted. You're just on the start of your journey.

cattledog:
Great news. Have you run the scale and the hall sensor reads together?

You can certainly test your system with tone(pin,1000) = 60,000 rpm but I don't know how realistic that is for your signals and the rise time of the hall sensor. I would expect that you could read 20,000 rpm. I think you bigger problem will be fast accelerations and throttle "goosing" in neutral. Depending on the scale reading and sd functions, you may not wind up reading rpm at faster than 10 times/second. I'm not certain how long the scale readings actually take as well. The rpm and the scale will be slightly out of sync, but time will tell if that is a major issue.

The SD writing can take a bit of time when the library's storage buffer is physically written to the card.

There's lots to read about Arduino dynamometers on Google and the website search. If you get very serious you may wind up on a processer with multiple cores to handle the rpm and load in a truly synchronous instead of high speed sequential fashion. You may find that spi FRAM, a solid state memory, is faster than the SD card.

Keep us posted. You're just on the start of your journey.

Yes hall and Load works together fine so far , I'll try bumping up the test freq and see what happens ,
I looked at all of those Ardyno types of dynos but they really did not fit my needs or were to much $$ , this design is for a LOW cost DIY type system . Im doing it for FUN really + learning , I got into these 1/5 scale Gas RC cars/trucks they are actually making a LOT of power from 32cc motors and up now .
So far this is the BEST combo RPM ,SD, LCD type code I have tried. and we got Loading in there ! .
I will keep you informed about my testing and post some Dyno runs .
I will add a EGT Exhaust gas temp. sensor to my tests later , might try to add it to this code ?? It used a Max6675 board , now that I see how you are doing the code with the cases.

Ok Im trying a test to see IF I can add a thermocouple reading to this sketch , I am not understanding the format , what am I doing wrong/missing ?? line 178 has errors and wont compile " Temp = Temp.thermocouple.readFahrenheit(); "

Ok I nulled that line and it compiled now to see if it works LOL

/*
  SD card read/write

  This example shows how to read and write data to and from an SD card file
  The circuit:
  SD card attached to SPI bus as follows:
** MOSI - pin 11
** MISO - pin 12
** CLK - pin 13
** CS - pin 4 (for MKRZero SD: SDCARD_SS_PIN)
*/
#include "HX711.h"
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <max6675.h>

LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x27, 16, 2);

#define calibration_factor -9300.0 //This value is obtained using the SparkFun_HX711_Calibration sketch
#define LOADCELL_DOUT_PIN  6
#define LOADCELL_SCK_PIN   7
HX711 scale;
float scaleReading;
float Temp;

#include <SPI.h>
#include <SD.h>
#define FILE_BASE_NAME "Data"
File file;
const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1;
char fileName[] = FILE_BASE_NAME "00.csv";
const int chipSelect = 4;

int thermoDO = 8;
int thermoCS = 9;
int thermoCLK = 7;


MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO);

//rpm/interrupt variables
byte hallPin = 2;
byte numCount = 5; //rising edge count give numCount - 1 intervals to measure
volatile byte count;
volatile unsigned long startTime;
volatile unsigned long endTime;
volatile boolean finishCount = false;
unsigned long period;
unsigned int rpm;

//switch state indexes
#define readHall 1
#define readScale 2
#define writeSD 3
#define Temp 4

byte state = readHall;

void isrCount()
{
  if (count == 0)//first entry to isr
  {
    startTime = micros();
  }
  if (count == numCount)
  {
    endTime = micros();
    detachInterrupt(digitalPinToInterrupt(hallPin));
    finishCount = true;
  }
  count++; //increment after test for numCount
}

void setup() {
  //lcd.begin();//not certain of right command for your library?
  lcd.begin();
  lcd.backlight();
  //lcd print unchanging aspects of display
  lcd.setCursor(0, 0);
  lcd.print("RPM = ");//print values at cursor 6,0
  lcd.setCursor(0, 1);
  lcd.print("Load = ");//print values at cursor 7,1

  Serial.begin(115200);
  //test output jumper pin 11 to pin 2
  pinMode(10, OUTPUT);
  tone(10, 50); //50Hz square wave 20ms period 50*60 = 3000 pulses/min
  //tone(10,40);//40Hz square wave 25ms per
  //tone(10, 3333); // 20k rpm 
  
  if (!SD.begin(chipSelect)) {
    Serial.println(F("begin failed"));
    return;
  }
  while (SD.exists(fileName)) {
    if (fileName[BASE_NAME_SIZE + 1] != '9') {
      fileName[BASE_NAME_SIZE + 1]++;
    } else if (fileName[BASE_NAME_SIZE] != '9') {
      fileName[BASE_NAME_SIZE + 1] = '0';
      fileName[BASE_NAME_SIZE]++;
    } else {
      Serial.println(F("Can't create file name"));
      return;
    }
  }
  //make the hall pin an input:
  pinMode(hallPin, INPUT);//maybe should be input pullup?

  scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
  scale.set_scale(calibration_factor); //This value is obtained by using the SparkFun_HX711_Calibration sketch
  scale.tare(); //Assuming there is no weight on the scale at start up, reset the scale to 0

  //test signal
  pinMode(10, OUTPUT);
  tone(10, 50); //50Hz square wave 20ms period 50*60 = 3000 pulses/min
  //tone(10,40);//40Hz square wave 25ms period 40*60 = 2400 pulses/min
  //tone(10, 333); // 20k rpm 
  attachInterrupt(digitalPinToInterrupt(hallPin), isrCount, RISING);//interrupt on pin 2
}

void loop() {
  switch (state) {
    case readHall:
      if (finishCount == true)
      {
        finishCount = false;//reset flag
        count = 0;
        period = (endTime - startTime);// micros
        //Serial.println(period);
        period = period / numCount; //5 intervals in measurement
        rpm =  60 * (1000000ul / period);
        Serial.print("RPM = ");
        Serial.println(rpm);
      // print lcd values at cursor 6,0
       lcd.setCursor(6, 0);
       // clear previous value
        lcd.print("         ");//9 spaces
        lcd.setCursor(6, 0);
        lcd.print(rpm);
        state = readScale;
      }
      break;

    case readScale:
     // Serial.println("read scale");
      scaleReading = scale.get_units(2);//two readings averaged
      Serial.print("Scale Reading OZ = ");//You can change this to kg but you'll need to refactor the calibration_factor
      Serial.println(scaleReading, 1);
      //print values at cursor 7,1
     // lcd.setCursor(7, 1);
      //clear previous value
     // lcd.print("        ");//8 spaces
      lcd.setCursor(7, 1);
      lcd.print(scaleReading, 1);
      state = writeSD;
      break;

    case writeSD:
      // Serial.println("write SD");
      file = SD.open(fileName, FILE_WRITE);
      if (!file) {
        Serial.println(F("open failed"));
        return;
      }
      file.print(rpm);
      file.print(",");
      file.println(scaleReading, 1);
      file.close();
      
      EIFR = bit (INTF0);//clear interrupt flag
      attachInterrupt(digitalPinToInterrupt(hallPin), isrCount, RISING);//interrupt on pin 2
      state = readHall;
      break;

       case Temp:
      Serial.println("Temp");
      Temp  = Temp.thermocouple.readFahrenheit();
      Serial.print("Temp = ");//You can change this to kg but you'll need to refactor the calibration_factor
      Serial.println(thermocouple.readFahrenheit());
      //print values at cursor 7,1
     // lcd.setCursor(7, 1);
      //clear previous value
     // lcd.print("        ");//8 spaces
      lcd.setCursor(7, 1);
      lcd.print(thermocouple.readFahrenheit());
      state = Temp;
      break;
  }
}

Ok I got the TEMP to work as well ! finally figured out the CASE thing ! nice , I do see one issue the RPM always change by 60 RPM ! always ? not sure why , the original code does this also unless I messed it up also ?

/*
  SD card read/write

  This example shows how to read and write data to and from an SD card file
  The circuit:
  SD card attached to SPI bus as follows:
** MOSI - pin 11
** MISO - pin 12
** CLK - pin 13
** CS - pin 4 (for MKRZero SD: SDCARD_SS_PIN)
*/
#include "HX711.h"
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <max6675.h>

LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x27, 16, 2);

#define calibration_factor -9300.0 //This value is obtained using the SparkFun_HX711_Calibration sketch
#define LOADCELL_DOUT_PIN  6
#define LOADCELL_SCK_PIN   7
HX711 scale;
float scaleReading;
float temp;

#include <SPI.h>
#include <SD.h>
#define FILE_BASE_NAME "Data"
File file;
const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1;
char fileName[] = FILE_BASE_NAME "00.csv";
const int chipSelect = 4;

int thermoDO = 8;
int thermoCS = 9;
int thermoCLK = 5;


MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO);

//rpm/interrupt variables
byte hallPin = 2;
byte numCount = 5; //rising edge count give numCount - 1 intervals to measure
volatile byte count;
volatile unsigned long startTime;
volatile unsigned long endTime;
volatile boolean finishCount = false;
unsigned long period;
unsigned int rpm;

//switch state indexes
#define readHall 1
#define readScale 2
#define writeSD 3
#define Temp 4

byte state = readHall;

void isrCount()
{
  if (count == 0)//first entry to isr
  {
    startTime = micros();
  }
  if (count == numCount)
  {
    endTime = micros();
    detachInterrupt(digitalPinToInterrupt(hallPin));
    finishCount = true;
  }
  count++; //increment after test for numCount
}

void setup() {
  //lcd.begin();//not certain of right command for your library?
  lcd.begin();
  lcd.backlight();
  //lcd print unchanging aspects of display
  lcd.setCursor(0, 0);
  lcd.print("RPM = ");//print values at cursor 6,0
  lcd.setCursor(0, 1);
  lcd.print("Ld");//print values at cursor 7,1
  lcd.setCursor(8, 1);
  lcd.print("Tmp");//print values at cursor 7,1

  Serial.begin(115200);
  //test output jumper pin 11 to pin 2
  pinMode(10, OUTPUT);
  tone(10, 50); //50Hz square wave 20ms period 50*60 = 3000 pulses/min
  //tone(10,40);//40Hz square wave 25ms per
  //tone(10, 3333); // 20k rpm

  if (!SD.begin(chipSelect)) {
    Serial.println(F("begin failed"));
    return;
  }
  while (SD.exists(fileName)) {
    if (fileName[BASE_NAME_SIZE + 1] != '9') {
      fileName[BASE_NAME_SIZE + 1]++;
    } else if (fileName[BASE_NAME_SIZE] != '9') {
      fileName[BASE_NAME_SIZE + 1] = '0';
      fileName[BASE_NAME_SIZE]++;
    } else {
      Serial.println(F("Can't create file name"));
      return;
    }
  }
  //make the hall pin an input:
  pinMode(hallPin, INPUT);//maybe should be input pullup?

  scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
  scale.set_scale(calibration_factor); //This value is obtained by using the SparkFun_HX711_Calibration sketch
  scale.tare(); //Assuming there is no weight on the scale at start up, reset the scale to 0

  //test signal
  pinMode(10, OUTPUT);
  tone(10, 50); //50Hz square wave 20ms period 50*60 = 3000 pulses/min
  //tone(10,40);//40Hz square wave 25ms period 40*60 = 2400 pulses/min
  //tone(10, 333); // 20k rpm
  attachInterrupt(digitalPinToInterrupt(hallPin), isrCount, RISING);//interrupt on pin 2
}

void loop() {
  switch (state) {
    case readHall:
      if (finishCount == true)
      {
        finishCount = false;//reset flag
        count = 0;
        period = (endTime - startTime);// micros
        //Serial.println(period);
        period = period / numCount; //5 intervals in measurement
        rpm =  60 * (1000000ul / period);
        Serial.print("RPM = ");
        Serial.println(rpm);
        // print lcd values at cursor 6,0
        lcd.setCursor(6, 0);
        // clear previous value
        lcd.print("         ");//9 spaces
        lcd.setCursor(6, 0);
        lcd.print(rpm);
        state = readScale;
      }
      break;

    case readScale:
      // Serial.println("read scale");
      scaleReading = scale.get_units(2);//two readings averaged
      Serial.print("Scale Reading OZ = ");//You can change this to kg but you'll need to refactor the calibration_factor
      Serial.println(scaleReading, 1);
      //print values at cursor 7,1
      // lcd.setCursor(7, 1);
      //clear previous value
      // lcd.print("        ");//8 spaces
      lcd.setCursor(3, 1);
      lcd.print(scaleReading, 1);
      state = Temp;
      break;

    case Temp:
      // Serial.println("Temp");
       temp = thermocouple.readFahrenheit();
      Serial.print("Temp = ");
      Serial.println(temp);
      //print values at cursor 7,1
      // lcd.setCursor(7, 1);
      //clear previous value
      // lcd.print("        ");//8 spaces
      lcd.setCursor(12, 1);
      lcd.print(temp);
     // delay(50);
      state = writeSD;
      break;

    case writeSD:
      // Serial.println("write SD");
      file = SD.open(fileName, FILE_WRITE);
      if (!file) {
        Serial.println(F("open failed"));
        return;
      }
      file.print(rpm);
      file.print(",");
      file.print(scaleReading, 1);
      file.print(",");
      file.println(temp);
      file.close();

      EIFR = bit (INTF0);//clear interrupt flag
      attachInterrupt(digitalPinToInterrupt(hallPin), isrCount, RISING);//interrupt on pin 2
      state = readHall;
      break;


  }
}

CATTLEDOG_NEW_CODE_4_temp.ino (5.2 KB)

It see a few issues in the latest code.

You are using the same pin as clock for two different devices. You may get away with this since you are using them sequentially, but if you have a free pin it would be best to put them on different pins.

#define LOADCELL_SCK_PIN   7
int thermoCLK = 7;

You can not have a variable and a case with the same name. You should also make your names more descriptive.

//float Temp;
float temperature;
//switch state indexes
#define readHall 1
#define readScale 2
#define writeSD 3
//#define Temp 4
#define readTemp 4

You also need to set the state for readTemp in some other case so that you will switch into it. To have all the data available for the SD write, I would have readScale code set state = readTemp, and the readTemp code set state = writeSD. Since temperature is a global variable you can use it in the writeSD block and it will have the last read value.

The readTemp case then looks like this

case readTemp:
      Serial.print("Temperature = ");
      temperature  = thermocouple.readFahrenheit();      
      Serial.println(temperature);
     // print values at cursor 7,1
     // lcd.setCursor(7, 1);
      //clear previous value
     // lcd.print("        ");//8 spaces
      lcd.setCursor(7, 1);
      lcd.print(temperature);
      state = writeSD;
      break;

You posted while I was sending a reply, and it looks like you fixed a few things without me. :slight_smile:

I do see one issue the RPM always change by 60 RPM !

There is nothing in the code I see which gets RPM by dividing crank rpm by the gear ratio. Where are you getting this value from in the spread sheet?

I think your issue has to do with integer math and truncation as you are getting RPM from crank rpm and the gear ratio of 5.7. Can you make the division of crank rpm by 5.7 be a floating point number?

I convert the RPM to crank RPM in the spreadsheet , I use the total gear ratio of 5.7 on this car to get the crank speed , I figured out that I was using the same name and YA it should be a better name to track it in code .

I didnt see that ! I'll check into it

#define LOADCELL_SCK_PIN   7
int thermoCLK = 7;

I tested your original code and my RPM change by 60 every time , in the serial/SD/LCD so it is in the code that this min. is being set right here I think ? , maybe do the math after this ? I use a drill and run my rig varying the RPM and the min change is 60rpm's

 case readHall:
      if (finishCount == true)
      {
        finishCount = false;//reset flag
        count = 0;
        period = (endTime - startTime);// micros
        //Serial.println(period);
        period = period / numCount; //5 intervals in measurement
        rpm =  60 * (1000000ul / period);
        Serial.print("RPM = ");
        Serial.println(rpm);

Oh and Merry Xmas

So I was going over this line , so I did some math and this does not make sense to me OR there are numbers being lost/ cut off ? I divided1 mil. / 362828 = 2.756 * 60 = 165.36 and you can see the RPM in the serial mon . is 660 ??

  rpm =  60 * (1000000ul / period);

my RPM change by 60 every time

Yes, I made a wrong choice when I used integer math which truncates instead of float math to calculate rpm. I did not realize you would be running so slowly, and the step errors are large. Basically you are multiplying numbers like 10,11, or 12 times 60, and each step is a minimum of 60 rpm.

We can fix that by going back to float math. I have attached some test code which demonstrates rpm declared as a float.

I have also changed the test signal to use TimerOne.h library. It enables a lot more low end range than tone() and can also provide a short pulse length which better emulates the magnet. Timer1 output is on pin 10 which is jumpered to pin 2. If you are using pin 10 for something else, try to free it up as the TimerOne pwm output is only on pin 9 or 10. If you can't do that, let me know as there is a work around for any pin.

https://www.pjrc.com/teensy/td_libs_TimerOne.html

Concerning your math anomalies, I can not confirm what you saw. The only way I can reproduce the 660 rpm is when I divide the period by 4 instead of 5. I know that there was some early confusion about whether to divide the multiple pulse period by numCount or numCount-1 to get the average pulse length. I believe that you may have an older code version using divide by 4 to get the 660. Please check carefully what you have, and verify the version.

Anyway, play with this new code, and see if the rpm calculations look better to you. When you integrate it with the complete code, please post the updated version.

#include <TimerOne.h>

#define readHall 1
#define readScale 2
#define writeSD 3
#define readTemp 4

byte hallPin = 2;
byte state = readHall;
byte timerTestOutputPin = 10;

//rpm/interrupt variables
byte numCount = 5; //rising edge counts 
volatile byte count = 0;
volatile unsigned long startTime;
volatile unsigned long endTime;
volatile boolean finishCount = false;
unsigned long period;
//unsigned int rpm;
float rpm;

void isrCount()
{
  if (count == 0)//first entry to isr
  {
    startTime = micros();
  }
  if (count == numCount)
  {
    endTime = micros();
    detachInterrupt(digitalPinToInterrupt(hallPin));
    finishCount = true;
  }
  count++; //increment after test for numCount
}

void setup() {  
  //Timer1.initialize(20000); //20 ms pulse length 50Hz = 3000 rpm
  //Timer1.initialize(50000); //50 ms pulse length 20 Hz = 1200 rpm
  //Timer1.initialize(100000); //100 ms pulse length 10 Hz = 600 rpm
  //Timer1.initialize(200000);//200ms/pulse 5Hz = 300 rpm
  //Timer1.initialize(225000);//225 ms pulse 4.5 Hz 270 rpm
  //Timer1.initialize(240000);//240 ms pulse 4.167 Hz 250 rpm
  //Timer1.initialize(250000);//250ms/pulse 4Hz = 240 rpm
  Timer1.initialize(266667); //267 ms pulse 3.75Hz = 225 rpm 
  //Timer1.initialize(500000);//500ms/pulse 2Hz = 120 rpm
  
  Serial.begin(115200); 
  pinMode(hallPin,INPUT);
  EIFR = bit (INTF0);//clear any interrupt flag
  attachInterrupt(digitalPinToInterrupt(hallPin), isrCount, RISING);//interrupt on pin 2
  //test output jumper pin 10 to pin 2
  //0-1023 10 bit duty cycle
  Timer1.pwm(timerTestOutputPin, 5);//short pulse aprox .5% duty cycle emulate magnet 
}

void loop() {
  switch (state)
  {
    case readHall:
      if (finishCount == true)
      {
        finishCount = false;//reset flag
        count = 0;
        period = (endTime - startTime);// micros
        Serial.println(period);
        float avgPeriod = period / numCount; //5 intervals in measurement
        Serial.println(avgPeriod,1);//float
        rpm = 60.0 *(1000000/avgPeriod);
        Serial.print("RPM = ");
        Serial.println(rpm,1);
        state = readScale;
      }
      break;
      
    case readScale:
      Serial.println("read scale");
      delay(50);///emulate time in case
      state = readTemp; //writeSD;
      break;

    case writeSD:
      Serial.println("write SD");
      delay(50);//emulate time in case
      EIFR = bit (INTF0);//clear interrupt flag
      attachInterrupt(digitalPinToInterrupt(hallPin), isrCount, RISING);//interrupt on pin2
      state = readHall;
      break;

      case readTemp:
       Serial.println("readTemp");
       delay(50);//emulate time in case
       state = writeSD;
       break;
  }
}