i have a functioning but noisy analog input code and now a nice clean digital code for rpm reading but can not for the life of me get the outputs put in the digital code.
const byte analogPin = A0; // Analog pin connected to the Hall sensor
const byte pwmPin1 = 9; // PWM output pin 1
const byte pwmPin2 = 10; // PWM output pin 2
volatile unsigned long pulseStartTime = 0; // Start time for pulse measurement
volatile unsigned long pulseEndTime = 0; // End time for pulse measurement
volatile byte pulseCount = 0; // Counter for pulses
const int threshold = 716; // Analog threshold value for 3.5V
unsigned long previousMillis = 0; // Stores the last time the loop ran
const long interval = 10; // Desired interval in milliseconds
// Constants for RPM and PWM
const int idleRPMThreshold = 1100; // Idle RPM threshold
const int idlePWMDutyCycle = 10; // Idle PWM duty cycle value (percentage)
const int firstRangeRPMMin = 1100; // Minimum RPM for the first range
const int firstRangeRPMMax = 4500; // Maximum RPM for the first range
const int firstRangeStartPWM = 100; // Start PWM percentage for the first range (100%)
const int firstRangeEndPWM = 50; // End PWM percentage for the first range (50%)
const int secondRangeRPMMin = 4500; // Minimum RPM for the second range
const int secondRangeRPMMax = 7000; // Maximum RPM for the second range
const int secondRangeStartPWM = 50; // Start PWM percentage for the second range (50%)
const int secondRangeEndPWM = 40; // End PWM percentage for the second range (40%)
void setup() {
Serial.begin(9600);
pinMode(analogPin, INPUT);
pinMode(pwmPin1, OUTPUT);
pinMode(pwmPin2, OUTPUT);
}
void loop() {
unsigned long currentMillis = millis();
// Check if the desired interval has passed
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis; // Save the last time the loop ran
int sensorValue = analogRead(analogPin); // Read the analog value from the sensor
// Check for falling edge (voltage drop)
if (sensorValue < threshold) {
if (pulseCount == 0) {
pulseStartTime = micros(); // Start timing when the first pulse is detected
}
pulseCount++; // Increment pulse count
// Check if four pulses have been detected
if (pulseCount == 4) {
pulseEndTime = micros(); // End timing after the fourth pulse
unsigned long timeInterval = pulseEndTime - pulseStartTime;
// Calculate RPM from the time interval of four pulses
float crankshaftRPM = calculateCrankshaftRPM(timeInterval);
int pwmValue = mapRPMToPWM(crankshaftRPM);
analogWrite(pwmPin1, pwmValue);
analogWrite(pwmPin2, pwmValue);
// Print the RPM and the corresponding PWM percentage
float pwmPercentage = (pwmValue / 255.0) * 100.0;
Serial.print("RPM: ");
Serial.print(crankshaftRPM);
Serial.print(" | PWM: ");
Serial.print(pwmPercentage);
Serial.println("%");
pulseCount = 0; // Reset pulse count for the next measurement
}
// Wait until the signal rises above the threshold again
while (analogRead(analogPin) < threshold) {
delay(1); // Small delay to avoid busy-waiting
}
}
}
}
float calculateCrankshaftRPM(unsigned long interval) {
// Calculate RPM from the time interval for four pulses
// Multiply by 2 because the camshaft is at half the speed of the crankshaft
// 1,000,000 us in a second, 60 seconds in a minute, 4 pulses per revolution
// The previous correction factor was 980 / 365. Adjusting to 1000 / 850.
float currentCorrectionFactor = 980.0 / 365.0;
float adjustmentFactor = 1000.0 / 850.0;
float finalCorrectionFactor = currentCorrectionFactor * adjustmentFactor;
return (1000000.0 / interval) * 60 * 2 / 4 * finalCorrectionFactor;
}
int mapRPMToPWM(float rpm) {
if (rpm <= idleRPMThreshold) {
return mapPercentageToPWM(idlePWMDutyCycle);
} else if (rpm <= firstRangeRPMMax) {
return mapRPMToPWM_FirstRange(rpm);
} else {
return mapRPMToPWM_SecondRange(rpm);
}
}
int mapRPMToPWM_FirstRange(float rpm) {
// Map RPM to PWM duty cycle for the first range (1100 to 4500 RPM)
float pwmPercentage = map(rpm, firstRangeRPMMin, firstRangeRPMMax, firstRangeStartPWM, firstRangeEndPWM);
return mapPercentageToPWM(pwmPercentage);
}
int mapRPMToPWM_SecondRange(float rpm) {
// Map RPM to PWM duty cycle for the second range (4500 to 7000 RPM)
float pwmPercentage = map(rpm, secondRangeRPMMin, secondRangeRPMMax, secondRangeStartPWM, secondRangeEndPWM);
return mapPercentageToPWM(pwmPercentage);
}
int mapPercentageToPWM(float percentage) {
// Convert percentage (0-100) to PWM value (0-255)
return (percentage / 100.0) * 255.0;
}
digital reading
#define HALL_SENSOR_PIN 2 // Pin connected to the hall sensor
#define TEETH_PER_REVOLUTION 8
#define PATTERN_LENGTH 8
volatile unsigned long lastToothTime = 0;
unsigned long toothDurations[PATTERN_LENGTH];
int toothIndex = 0;
unsigned long revolutionStartTime = 0;
unsigned long revolutionEndTime = 0;
unsigned long revolutionDuration = 0;
double rpm = 0.0;
// Correction factor calculated based on actual vs. measured RPM
double correctionFactor = 3.34; // Placeholder value, adjust this based on calibration
void setup() {
Serial.begin(9600);
pinMode(HALL_SENSOR_PIN, INPUT);
// Attach interrupt to the hall sensor pin
attachInterrupt(digitalPinToInterrupt(HALL_SENSOR_PIN), countTooth, RISING);
}
void loop() {
// Calculate RPM if a full revolution has been detected
if (toothIndex == PATTERN_LENGTH) {
revolutionEndTime = millis();
revolutionDuration = revolutionEndTime - revolutionStartTime;
// Calculate RPM: 60000 ms per minute divided by time per revolution in ms
rpm = (60000.0 / revolutionDuration) * correctionFactor;
// Print the RPM to the Serial Monitor
Serial.print("RPM: ");
Serial.println(rpm);
// Reset the tooth index for the next revolution
toothIndex = 0;
revolutionStartTime = revolutionEndTime;
}
}
void countTooth() {
static unsigned long lastDebounceTime = 0;
unsigned long currentTime = millis();
// Debounce the input to ensure stable readings
if ((currentTime - lastDebounceTime) > 10) { // 10 ms debounce time
if (toothIndex == 0) {
revolutionStartTime = currentTime;
} else {
// Calculate time difference between the current and last tooth
toothDurations[toothIndex - 1] = currentTime - lastToothTime;
}
lastToothTime = currentTime;
toothIndex++;
lastDebounceTime = currentTime;
// Detect the known pattern to confirm a revolution
if (toothIndex >= PATTERN_LENGTH && verifyPattern()) {
toothIndex = 0;
}
}
}
// Function to verify the specific tooth pattern
bool verifyPattern() {
// Define expected pattern in milliseconds for comparison
unsigned long smallThreshold = 30; // Threshold for small teeth
unsigned long bigThreshold = 50; // Threshold for big teeth
// Check if the detected pattern matches 2 small, 2 big, 2 small, 2 big
return (toothDurations[0] <= smallThreshold && toothDurations[1] <= smallThreshold &&
toothDurations[2] >= bigThreshold && toothDurations[3] >= bigThreshold &&
toothDurations[4] <= smallThreshold && toothDurations[5] <= smallThreshold &&
toothDurations[6] >= bigThreshold && toothDurations[7] >= bigThreshold);
}
any help to get this together is greatly appreciated!
the first thing to get working is to reliably count events.
To determine speed, you need to see how many events occurred within some period of time (e.g. 1 sec). so you need to capture the count each processing cycle (e.g. 1 sec) and subtract from that # the count in the previous cycle.
using interrupts, as shown by kolaha is a good approach. the interrupt routine, countTooth(), will be executed whenever interrupt input pin goes HIGH, RISING, even though code in loop may be waiting for the timer to expire.
however, if the count being increment in the interrupt is more than a byte, interrupts need to be disabled when the value is copied. use nointerrupts() and interrupts() around that copy
there's plenty of time during the processing cycle to do stuff
i think there's an interrupt for each tooth and there are 8 teeth/rev. At 1000 rpm, 8000 teeth/min, 133.3 teeth/sec, the time between events is 7.5 msec. of course smaller at higher rpm.
looks like your countTooth() doesn't do anything if invoked sooner than 10 msec since the last time it did anything
i can only make codes that chatbot helps me with as it is instantly availible. this codes does run real nice on the engine.
#define HALL_SENSOR_PIN 2 // Pin connected to the hall sensor
#define TEETH_PER_REVOLUTION 8
#define PATTERN_LENGTH 8
volatile unsigned long lastToothTime = 0;
unsigned long toothDurations[PATTERN_LENGTH];
int toothIndex = 0;
unsigned long revolutionStartTime = 0;
unsigned long revolutionEndTime = 0;
unsigned long revolutionDuration = 0;
double rpm = 0.0;
// Correction factor calculated based on actual vs. measured RPM
double correctionFactor = 3.34; // Placeholder value, adjust this based on calibration
void setup() {
Serial.begin(9600);
pinMode(HALL_SENSOR_PIN, INPUT);
// Attach interrupt to the hall sensor pin
attachInterrupt(digitalPinToInterrupt(HALL_SENSOR_PIN), countTooth, RISING);
}
void loop() {
// Calculate RPM if a full revolution has been detected
if (toothIndex == PATTERN_LENGTH) {
revolutionEndTime = millis();
revolutionDuration = revolutionEndTime - revolutionStartTime;
// Calculate RPM: 60000 ms per minute divided by time per revolution in ms
rpm = (60000.0 / revolutionDuration) * correctionFactor;
// Print the RPM to the Serial Monitor
Serial.print("RPM: ");
Serial.println(rpm);
// Reset the tooth index for the next revolution
toothIndex = 0;
revolutionStartTime = revolutionEndTime;
}
}
void countTooth() {
static unsigned long lastDebounceTime = 0;
unsigned long currentTime = millis();
// Debounce the input to ensure stable readings
if ((currentTime - lastDebounceTime) > 10) { // 10 ms debounce time
if (toothIndex == 0) {
revolutionStartTime = currentTime;
} else {
// Calculate time difference between the current and last tooth
toothDurations[toothIndex - 1] = currentTime - lastToothTime;
}
lastToothTime = currentTime;
toothIndex++;
lastDebounceTime = currentTime;
// Detect the known pattern to confirm a revolution
if (toothIndex >= PATTERN_LENGTH && verifyPattern()) {
toothIndex = 0;
}
}
}
// Function to verify the specific tooth pattern
bool verifyPattern() {
// Define expected pattern in milliseconds for comparison
unsigned long smallThreshold = 30; // Threshold for small teeth
unsigned long bigThreshold = 50; // Threshold for big teeth
// Check if the detected pattern matches 2 small, 2 big, 2 small, 2 big
return (toothDurations[0] <= smallThreshold && toothDurations[1] <= smallThreshold &&
toothDurations[2] >= bigThreshold && toothDurations[3] >= bigThreshold &&
toothDurations[4] <= smallThreshold && toothDurations[5] <= smallThreshold &&
toothDurations[6] >= bigThreshold && toothDurations[7] >= bigThreshold);
}
i had chatbot just count 8 teeth as 1 crank rotation and then get rpm by elapsed time
i am thinking counting 8 times and then calculating the elapsed time to get rpm would be fine. the serial monitor backs this up....
anyways, ill just start thinking out loud and feel free to tap in....
so this is my product from the fully functioning read RPM code
// Calculate RPM: 60000 ms per minute divided by time per revolution in ms
rpm = (60000.0 / revolutionDuration) * correctionFactor;
// Print the RPM to the Serial Monitor
Serial.print("RPM: ");
Serial.println(rpm);
so i want to set map functions based off this integer right?
it is stored globally right?
double rpm = 0.0;
i am thinking if i just drop the decimal i get whole numbers?
double rpm = 0;
so now i want to have my adjustable outputs by what rpm is stored
// Constants for RPM and PWM
const int idleRPMThreshold = 1100; // Idle RPM threshold
const int idlePWMDutyCycle = 10; // Idle PWM duty cycle value (percentage)
const int firstRangeRPMMin = 1100; // Minimum RPM for the first range
const int firstRangeRPMMax = 4500; // Maximum RPM for the first range
const int firstRangeStartPWM = 100; // Start PWM percentage for the first range (100%)
const int firstRangeEndPWM = 50; // End PWM percentage for the first range (50%)
const int secondRangeRPMMin = 4500; // Minimum RPM for the second range
const int secondRangeRPMMax = 7000; // Maximum RPM for the second range
const int secondRangeStartPWM = 50; // Start PWM percentage for the second range (50%)
const int secondRangeEndPWM = 40; // End PWM percentage for the second range (40%)
in the loop
int mapRPMToPWM(float rpm) {
if (rpm <= idleRPMThreshold) {
return mapPercentageToPWM(idlePWMDutyCycle);
} else if (rpm <= firstRangeRPMMax) {
return mapRPMToPWM_FirstRange(rpm);
} else {
return mapRPMToPWM_SecondRange(rpm);
}
}
int mapRPMToPWM_FirstRange(float rpm) {
// Map RPM to PWM duty cycle for the first range (1100 to 4500 RPM)
float pwmPercentage = map(rpm, firstRangeRPMMin, firstRangeRPMMax, firstRangeStartPWM, firstRangeEndPWM);
return mapPercentageToPWM(pwmPercentage);
}
int mapR
1 not sure what define# does but it worked
2 PWM outputs to my VVT solenoids
3 used to count 8 teeth and make rpm right?
4 my goal is to be able to adjust the outputs by rpm ranges
5 digital read of the teeth was off so it was scaled and then perfect
6 easy stuff
7 not sure how interrupts work
1 starts measuring time or how ever millis is explained
2 i think the print stuff is not in the right spot. the digital read had some here before.
3 end of tooth time measuring?
and last the tooth measuring that gets the magic "RPM" int value right? the money shot....
1 i believe it is looking for the 2 small gaps then 2 large gaps as is the toothed wheel pattern
2 i think this just converts from printing 0-255 output to a percentage number
3 this was up in the beginning
// Constants for RPM and PWM
const int idleRPMThreshold = 1100; // Idle RPM threshold
const int idlePWMDutyCycle = 10; // Idle PWM duty cycle value (percentage)
i ran kolaha's code and got about 1/4 speed rpm
i am thinking i need a 100 milli delay instead of 1000 for performance
at 1000 rpm idle serial monitor reads
ok, #1 so, my first code does great at reading the 2 different cycles.
stable and accurate but i need the middle average of the two.
#2 so i have a code to try and average out the two cycles but it prints double the actual speed. no longer split serial prints though.....
#3 i control Z back to the split code (same project) and now the serial monitor reads the perfect average! great but weird right?
to be sure i just re-upload the first code and now get the split cycle again. what!
how does my desired outcome not be in either code directly but in the transition from teh 2nd to the 1st???? i have done this a couple times. it is repeatable