Hey guys,
We made a sensor package at work here and we're having issues with the tach at lower speeds. Below 100 RPM the code doesn't want to show less than that. Anywhere between 120-300 RPM there's no problem and it's bang on to a photo tach. We started out with counting teeth over a fixed point in time, changed to measuring the interval. Tried a 20 tooth wheel, and a 40 tooth wheel. We've been able to improve things, but not getting it to where we need. Not too sure where to go with it. Is it too much for the board to handle. We are using a Mega board. Thanks!
//Version notes 6.5
//Updated Tachometer to measure RPM based on time taken for 5 counts, rather than counting teeth over 1/4 second
//Updated RPMtimeout to 500 miliseconds for better slow speed tachometer readings.
//Changed Tooth count to a 40 tooth even spaced tone wheel to get better low speed measurements.
//Changed tachometer to rising instead of falling
#include <DFRobot_HX711_I2C.h>
#include <DFRobot_LIS2DW12.h>
#include <LiquidCrystal_I2C.h>
#include <RunningAverage.h>
#include "LSM6DS3.h"
#include <Wire.h>
// Sampling rate
const int frequency = 5;
const unsigned long measurementInterval = 1000 / frequency;
// ───────── Load Cells ─────────
DFRobot_HX711_I2C Scale1(&Wire, 0x66);
DFRobot_HX711_I2C Scale2(&Wire, 0x64);
float tension1 = 0.0, tension2 = 0.0;
float scale1cal = 999.26;
float scale2cal = 685.34;
// ───────── Accelerometers ─────────
DFRobot_LIS2DW12_I2C acce2(&Wire, 0x19); // LIS2DW12
LSM6DS3 myIMU(I2C_MODE, 0x6A); // LSM6DS3
float x2, y2, z2, pitch2, roll2;
RunningAverage RApitch2(5);
RunningAverage RAroll2(5);
float ax, ay, az, pitch, roll;
// ───────── Tachometer ─────────
const byte irSensorPin = 3;
const byte teethPerRevolution = 40;
volatile unsigned long lastToothMicros = 0;
volatile unsigned long toothIntervalSum = 0;
volatile byte toothCount = 0;
volatile bool newToothBatch = false;
float rpm = 0.0;
RunningAverage RArpm(5);
// ───────── Pressure Sensor ─────────
const int PressureSensor = A0;
const int numReadings = 4;
int readings[numReadings] = {0}, readIndex = 0, total = 0, average = 0;
float Press_cal = 9.18;
// ───────── LCD ─────────
LiquidCrystal_I2C lcd(0x27, 20, 4);
// ───────── Flow Meter ─────────
const byte sensorPin = 2;
const byte sensorInterrupt = 0;
const byte statusLed = 13;
float calibrationFactor = 24.98;
volatile byte pulseCountflow = 0;
float flowRate = 0.0;
unsigned int flowMilliLitres = 0;
float totalMilliLitres = 0.0;
// ───────── Setup ─────────
void setup() {
Serial.begin(9600);
Wire.begin();
// LCD setup
lcd.init();
lcd.backlight();
lcd.clear();
// Accelerometer 1 (LIS2DW12)
while (!acce2.begin()) {
Serial.println("3 Axis Offline");
lcd.setCursor(0, 0); lcd.print("3 Axis Offline");
delay(1000);
}
acce2.softReset();
acce2.continRefresh(true);
acce2.setDataRate(DFRobot_LIS2DW12::eRate_50hz);
acce2.setRange(DFRobot_LIS2DW12::e2_g);
acce2.setFilterPath(DFRobot_LIS2DW12::eLPF);
acce2.setFilterBandwidth(DFRobot_LIS2DW12::eRateDiv_4);
acce2.setPowerMode(DFRobot_LIS2DW12::eContLowPwrLowNoise2_14bit);
RApitch2.clear(); RAroll2.clear();
Serial.println("3 Axis Online");
lcd.setCursor(0, 0); lcd.print("3 Axis Online");
delay(1000);
// Accelerometer 2 (LSM6DS3)
if (myIMU.begin() != 0) {
Serial.println("6 Axis Offline");
lcd.setCursor(0, 1);
lcd.print("6 Axis Offline");
} else {
Serial.println("6 Axis Online");
lcd.setCursor(0, 1);
lcd.print("6 Axis Online");
delay(1000);
}
// Load Cells
while (!Scale1.begin()) {
Serial.println("Load cell 1 Offline");
lcd.setCursor(0, 2);
lcd.print("LC 1 Offline");
delay(1000);
}
Scale1.setCalibration(scale1cal);
Serial.println("Load cell 1 Online");
lcd.setCursor(0, 2);
lcd.print("LC 1 Online");
delay(1000);
while (!Scale2.begin()) {
Serial.println("Load cell 2 Offline");
lcd.setCursor(0, 3);
lcd.print("LC 2 Offline");
delay(1000);
}
Scale2.setCalibration(scale2cal);
Serial.print("Load cell 2 Online");
lcd.setCursor(0, 3);
lcd.print("LC 2 Online");
delay(1000);
delay(3000);
//Set up LCD Display
lcd.clear();
lcd.setCursor(2, 0); lcd.print("Boat #1 Boat #2");
lcd.setCursor(0, 1); lcd.print("P:");
lcd.setCursor(0, 2); lcd.print("T:");
lcd.setCursor(6, 3); lcd.print("RPM:");
// Tachometer
pinMode(irSensorPin, INPUT_PULLUP);
pinMode(11, OUTPUT); digitalWrite(11, HIGH);
attachInterrupt(digitalPinToInterrupt(irSensorPin), countTooth, FALLING);
// Pressure Sensor
for (int i = 0; i < numReadings; i++) readings[i] = 0;
// Flow Meter
pinMode(statusLed, OUTPUT); digitalWrite(statusLed, HIGH);
pinMode(sensorPin, INPUT); digitalWrite(sensorPin, HIGH);
attachInterrupt(sensorInterrupt, pulseCounter, FALLING);
}
// ───────── Main Loop ─────────
void loop() {
static unsigned long lastLoopTime = 0;
unsigned long currentMillis = millis();
if (currentMillis - lastLoopTime >= measurementInterval) {
lastLoopTime = currentMillis;
readAccelerometers();
readTachometer(currentMillis);
readPressureSensor();
readFlowMeter(currentMillis);
readLoadCells();
updateLCD();
outputSerialData(currentMillis);
}
}
// ───────── Modular Functions ─────────
void readAccelerometers() {
//Read 6 axis
ay = myIMU.readFloatAccelX();
ax = myIMU.readFloatAccelY();
az = myIMU.readFloatAccelZ();
roll = atan2(ay, az) * 57.3;
pitch = -(atan2(-ax, sqrt(ay * ay + az * az)) * 57.3);
//Read 3 axis
x2 = acce2.readAccX();
y2 = acce2.readAccY();
z2 = acce2.readAccZ();
roll2 = atan2(y2, z2) * 57.3;
pitch2 = atan2(-x2, sqrt(y2 * y2 + z2 * z2)) * 57.3;
RAroll2.addValue(roll2);
RApitch2.addValue(pitch2);
}
void readTachometer(unsigned long now) {
static unsigned long lastRPMUpdate = 0;
static unsigned long rpmTimeout = 500;
noInterrupts();
bool batchReady = newToothBatch;
unsigned long intervalSum = toothIntervalSum;
byte count = toothCount;
newToothBatch = false;
toothIntervalSum = 0;
toothCount = 0;
if (batchReady && count >= 1 && intervalSum > 0) {
float avgToothInterval = intervalSum / float(count);
float toothFrequency = 1e6 / avgToothInterval;
rpm = (toothFrequency * 60.0) / teethPerRevolution;
lastRPMUpdate = now;
} else if (now - lastRPMUpdate > rpmTimeout) {
rpm = 0;
}
RArpm.addValue(rpm);
interrupts();
}
void readPressureSensor() {
total -= readings[readIndex];
readings[readIndex] = ((analogRead(PressureSensor) - 86) / Press_cal);
total += readings[readIndex];
readIndex = (readIndex + 1) % numReadings;
average = total / numReadings;
}
void readFlowMeter(unsigned long now) {
static unsigned long lastFlowTime = 0;
if ((now - lastFlowTime) > measurementInterval) {
detachInterrupt(sensorInterrupt);
flowRate = ((1000.0 / (now - lastFlowTime)) * pulseCountflow) / calibrationFactor;
lastFlowTime = now;
flowMilliLitres = (flowRate / 60) * 1000;
totalMilliLitres += flowMilliLitres;
pulseCountflow = 0;
attachInterrupt(sensorInterrupt, pulseCounter, FALLING);
}
}
void readLoadCells() {
tension1 = Scale1.readWeight(1);
tension2 = Scale2.readWeight(1);
}
void updateLCD() {
lcd.setCursor(2,1); lcd.print(" ");
lcd.setCursor(2,2); lcd.print(" ");
lcd.setCursor(10,3); lcd.print(" ");
lcd.setCursor(2, 1); lcd.print(pitch,2);
lcd.setCursor(11, 1); lcd.print(RApitch2.getAverage(),2);
lcd.setCursor(2, 2); lcd.print(tension1,1);
lcd.setCursor(11, 2); lcd.print(tension2,1);
lcd.setCursor(10, 3); lcd.print(RArpm.getAverage(),0);
}
void outputSerialData(unsigned long now) {
Serial.print(now / 1000.0, 2); Serial.print(",");
Serial.print(average); Serial.print(",");
Serial.print(flowRate); Serial.print(",");
Serial.print(RArpm.getAverage()); Serial.print(",");
Serial.print(tension1); Serial.print(",");
Serial.print(tension2); Serial.print(",");
Serial.print(pitch); Serial.print(",");
Serial.print(roll); Serial.print(",");
Serial.print(RApitch2.getAverage()); Serial.print(",");
Serial.print(RAroll2.getAverage()); Serial.println();
}
// ───────── Interrupts ─────────
void countTooth() {
static unsigned long prevToothMicros = 0;
unsigned long now = micros();
unsigned long interval = now - prevToothMicros;
if (interval > 1000 ) {
toothIntervalSum += interval;
prevToothMicros = now;
toothCount++;
if (toothCount >= 10) {
lastToothMicros = now;
newToothBatch = true;
}
}
}
void pulseCounter() {
pulseCountflow++;
}