The detach interrupt was placed there because it was effecting the lcd. both functions have prints in the same area on the lcd and they were writing on top of each other. and in the rare cases when i'm counting, i don't want anything else is going on the background. That's why i orignially put it there. But since i decided not to share the hall signal between 2 pins, and redirect the signal, the interrupt pin will be disconnected, so the detach interrupt probably isn't needed. I'm still tinkering with it.
i checked and the detach interrupt isn't needed in my code as long as the signal isn't hooked up. but i'm still attempting to get the counting number from the ISR alone
But since i decided not to share the hall signal between 2 pins, and redirect the signal, the interrupt pin will be disconnected
i'm still attempting to get the counting number from the ISR alone
Pick a lane.
Either use interrupts or poll a pin. Either approach will work.
I made a speedometer for my car using a Hall Effect sensor with a magnet attached to a drive shaft. I used the PulseIn function rather than an ISR. It worked perfectly at all speeds. The code was absolutely trivial.
I never experienced any bouncing effect. I've wondered why that was the case, but I don't really understand what causes bouncing. Does it only occur with mechanical switches and not with a switch acitivated by a magnetic field?
Why not use the switch to choose RPM mode or Turn mode?
void loop() {
checkSwitch();
if( switchState == LOW){
RPMcode();
} else {
Turns();
}
}
...then you wouldn't need to intertwine the conditional other-mode disabling code in your functions.
Oh...Turns()
is a blocking, infinite loop:
void Turns() {
if (switchState == HIGH) {
detachInterrupt(digitalPinToInterrupt(2)) ;
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(input) == 0) {
if (on_state == false) {
on_state = true;
pulse++;
}
}
else {
on_state = false;
}
lcd.setCursor(6, 1);
lcd.print(pulse);
}
delay(1); // Delay for stability.
}
}
The delay()
is never reached.
How noisy is your sensor? Maybe adapt this code and share some data:
// for Discord and Forum
// Code at https://wokwi.com/projects/327202747064517203
const byte buttonPin = 2;
void setup() {
// put your setup code here, to run once:
pinMode(buttonPin, INPUT_PULLUP);
Serial.begin(115200);
Serial.print("ButtonBounceDemo.ino -- Wokwi simulation of bouncing\n"
"If you push the button, the sketch will report the\n"
"microsecond time of the signal on Digital Pin 2.\n"
"This is good for checking for noisy buttons.\n"
);
}
//globals
int oldButtonState = LOW;
unsigned long lastTransitionUs=0;
unsigned long deadtime = 0; // deadtime for state latching mode debouncing
// set deaddtime above 0us to inhibit repeated transitions
// deadtime 2200us isn't quite enough to debounce the Wokwi pushbutton
void loop() {
// put your main code here, to run repeatedly:
int buttonState = digitalRead(buttonPin);
unsigned long now = micros();
if (buttonState != oldButtonState && (now - lastTransitionUs >= deadtime)) {
lastTransitionUs = now;
Serial.print(now);
Serial.print(buttonState ? "+ " : "- ");
oldButtonState = buttonState;
}
}
it turns out the hall effect wasn't noisy. when it was turning really slow, it was registering the hall effect multiple times. But that problem has been fixed.
That's a good idea. I'll try that today. I also have to add a couple buttons so i can manually add and subtract the count if needed.
I'd rig a button (maybe with a resistor) in parallel with the hall sensor, and another switch/button for forward/reverse so you could add turns with button presses, and switch over to reverse mode and unwind turns or count down.
Which of the dozens of different Hall effect sensors are you using, and how is it connected?
If it's a HES with open collector NPN output, you may only need a 10k pullup resistor.
Does anyone actually know if HE sensors exhibit a bounce effect or not?
what i ended up doing is adding 2 buttons that will add and subtract the count when needed.... like if mess up and have to unwrap a couple turns to fix them. and i basically have everything working. but i have something else happening that i cant quite figure out.
when i first turn it on, the default is the rpm code, and that is working fine. and i can flip the switch which turns on the counting... and that works fine. but if i switch the switch back to the rpm code, the display goes back to rpm like it should, but the rpm no longer works unless i hit the reset. which i can work with that since i rarely if ever switch back and forth, but i would like to at least understand why it's doing that. I thought it could be caused from the shared signal, but have tried it shared, and isolated and it doesn't make a difference.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27,16,2);
// LCD SDA = A4 pin
// LCD SDL = A5 pin
const byte rpmOff = 12;
#define input 11
unsigned long pulse = 0;
byte switchState;
const byte plusButton = 6;
const byte minusButton = 7;
byte plusState=0;
byte minusState=0;
const byte PulsesPerRevolution = 1; // 1 pulse per rev for hall sensor
// Set how many pulses there are on each revolution. Default: 2.
// If the period between pulses is too high, or even if the pulses stopped, then we would get stuck showing the
// last value instead of a 0. Because of this we are going to set a limit for the maximum period allowed.
// If the period is above this value, the RPM will show as 0.
// The higher the set value, the longer lag/delay will have to sense that pulses stopped, but it will allow readings
// at very low RPM.
// Setting a low value is going to allow the detection of stop situations faster, but it will prevent having low RPM readings.
// The unit is in microseconds.
const unsigned long ZeroTimeout = 2000000; // 100000 is roughly 600rpm
// For high response time, a good value would be 100000.
// For reading very low RPM, a good value would be 300000.
// Calibration for smoothing RPM:
const byte numReadings = 10; // Number of samples for smoothing. The higher, the more smoothing, but it's going to
// react slower to changes. 1 = no smoothing. Default: 2.... up to 10
volatile unsigned long LastTimeWeMeasured; // Stores the last time we measured a pulse so we can calculate the period.
volatile unsigned long PeriodBetweenPulses = ZeroTimeout+1000; // Stores the period between pulses in microseconds.
// It has a big number so it doesn't start with 0 which would be interpreted as a high frequency.
volatile unsigned long PeriodAverage = ZeroTimeout+1000; // Stores the period between pulses in microseconds in total, if we are taking multiple pulses.
// It has a big number so it doesn't start with 0 which would be interpreted as a high frequency.
unsigned long FrequencyRaw; // Calculated frequency, based on the period. This has a lot of extra decimals without the decimal point.
unsigned long FrequencyReal; // Frequency without decimals.
unsigned long RPM; // Raw RPM without any processing.
unsigned int PulseCounter = 1;
// Counts the amount of pulse readings we took so we can average multiple pulses before calculating the period.
unsigned long PeriodSum; // Stores the summation of all the periods to do the average.
unsigned long LastTimeCycleMeasure = LastTimeWeMeasured; // Stores the last time we measure a pulse in that cycle.
// We need a variable with a value that is not going to be affected by the interrupt
// because we are going to do math and functions that are going to mess up if the values
// changes in the middle of the cycle.
unsigned long CurrentMicros = micros(); // Stores the micros in that cycle.
// We need a variable with a value that is not going to be affected by the interrupt
// because we are going to do math and functions that are going to mess up if the values
// changes in the middle of the cycle.
// We get the RPM by measuring the time between 2 or more pulses so the following will set how many pulses to
// take before calculating the RPM. 1 would be the minimum giving a result every pulse, which would feel very responsive
// even at very low speeds but also is going to be less accurate at higher speeds.
// With a value around 10 you will get a very accurate result at high speeds, but readings at lower speeds are going to be
// farther from eachother making it less "real time" at those speeds.
// There's a function that will set the value depending on the speed so this is done automatically.
unsigned int AmountOfReadings = 1;
unsigned int ZeroDebouncingExtra; // Stores the extra value added to the ZeroTimeout to debounce it.
// The ZeroTimeout needs debouncing so when the value is close to the threshold it
// doesn't jump from 0 to the value. This extra value changes the threshold a little
// when we show a 0.
// Variables for smoothing tachometer:
unsigned long readings[numReadings]; // The input.
unsigned long readIndex; // The index of the current reading.
unsigned long total; // The running total.
unsigned long average;
void setup() {
lcd.init();
lcd.backlight();
lcd.setCursor(2,0);
lcd.print("Lathe Sensor");
pinMode(rpmOff, INPUT);
pinMode(input, INPUT_PULLUP);
pinMode(plusButton, INPUT_PULLUP);
pinMode(minusButton, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(2), Pulse_Event, FALLING); // Enable interruption pin 2 when going from LOW to HIGH.
delay(2000);
lcd.clear();
}
void loop() {
CheckSwitch();
//RPMcode();
//Turns();
}
void CheckSwitch(){
switchState = digitalRead(rpmOff);
if( switchState == LOW){
lcd.setCursor(0,0);
lcd.print("RPM");
RPMcode();
}
else {
lcd.setCursor(0,0);
lcd.print("Turns");
Turns();
}
}
void Turns(){
detachInterrupt(digitalPinToInterrupt(2)) ;
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(input)==0){
if (on_state==false){
on_state = true;
pulse++;
}
}
else{
on_state = false;
}
plusState = digitalRead(plusButton);
minusState = digitalRead(minusButton);
if(plusState == LOW){
pulse= pulse + 1;
delay(150);
}
if(minusState == LOW){
pulse= pulse - 1;
delay(150);
}
lcd.setCursor(6,1);
lcd.print(pulse);
if( plusState == LOW && minusState == LOW){
lcd.clear();
return;
}
}
}
void RPMcode(){ // We are going to do math and functions with those values and they can create glitches if they change in the
// middle of the cycle.
LastTimeCycleMeasure = LastTimeWeMeasured; // Store the LastTimeWeMeasured in a variable.
CurrentMicros = micros(); // Store the micros() in a variable.
// CurrentMicros should always be higher than LastTimeWeMeasured, but in rare occasions that's not true.
// I'm not sure why this happens, but my solution is to compare both and if CurrentMicros is lower than
// LastTimeCycleMeasure I set it as the CurrentMicros.
// The need of fixing this is that we later use this information to see if pulses stopped.
if(CurrentMicros < LastTimeCycleMeasure)
{
LastTimeCycleMeasure = CurrentMicros;
}
// Calculate the frequency:
FrequencyRaw = 10000000000 / PeriodAverage; // Calculate the frequency using the period between pulses.
// Detect if pulses stopped or frequency is too low, so we can show 0 Frequency:
if(PeriodBetweenPulses > ZeroTimeout - ZeroDebouncingExtra || CurrentMicros - LastTimeCycleMeasure > ZeroTimeout - ZeroDebouncingExtra)
{ // If the pulses are too far apart that we reached the timeout for zero:
FrequencyRaw = 0; // Set frequency as 0.
ZeroDebouncingExtra = 2000; // Change the threshold a little so it doesn't bounce.
}
else
{
ZeroDebouncingExtra = 0; // Reset the threshold to the normal value so it doesn't bounce.
}
FrequencyReal = FrequencyRaw / 10000; // Get frequency without decimals.
// This is not used to calculate RPM but we remove the decimals just in case
// you want to print it.
// Calculate the RPM:
RPM = FrequencyRaw / PulsesPerRevolution * 60; // Frequency divided by amount of pulses per revolution multiply by
// 60 seconds to get minutes.
RPM = RPM / 10000; // Remove the decimals.
// Smoothing RPM:
total = total - readings[readIndex]; // Advance to the next position in the array.
readings[readIndex] = RPM; // Takes the value that we are going to smooth.
total = total + readings[readIndex]; // Add the reading to the total.
readIndex = readIndex + 1; // Advance to the next position in the array.
if (readIndex >= numReadings) // If we're at the end of the array:
{
readIndex = 0; // Reset array index.
}
// Calculate the average:
average = total / numReadings; // The average value it's the smoothed result.
char string[10]; // Create a character array of 10 characters
// Convert float to a string:
dtostrf(average, 6, 0, string); // (<variable>,<amount of digits we are going to use>,<amount of decimal digits>,<string name>)
lcd.setCursor(0,1);
lcd.print(string);
} // End of loop.
void Pulse_Event() // The interrupt runs this to calculate the period between pulses:
{
PeriodBetweenPulses = micros() - LastTimeWeMeasured; // Current "micros" minus the old "micros" when the last pulse happens.
// This will result with the period (microseconds) between both pulses.
// The way is made, the overflow of the "micros" is not going to cause any issue.
LastTimeWeMeasured = micros(); // Stores the current micros so the next time we have a pulse we would have something to compare with.
if(PulseCounter >= AmountOfReadings) // If counter for amount of readings reach the set limit:
{
PeriodAverage = PeriodSum / AmountOfReadings; // Calculate the final period dividing the sum of all readings by the
// amount of readings to get the average.
PulseCounter = 1; // Reset the counter to start over. The reset value is 1 because its the minimum setting allowed (1 reading).
PeriodSum = PeriodBetweenPulses; // Reset PeriodSum to start a new averaging operation.
// Change the amount of readings depending on the period between pulses.
// To be very responsive, ideally we should read every pulse. The problem is that at higher speeds the period gets
// too low decreasing the accuracy. To get more accurate readings at higher speeds we should get multiple pulses and
// average the period, but if we do that at lower speeds then we would have readings too far apart (laggy or sluggish).
// To have both advantages at different speeds, we will change the amount of readings depending on the period between pulses.
// Remap period to the amount of readings:
int RemapedAmountOfReadings = map(PeriodBetweenPulses, 40000, 5000, 1, 10); // Remap the period range to the reading range.
// 1st value is what are we going to remap. In this case is the PeriodBetweenPulses.
// 2nd value is the period value when we are going to have only 1 reading. The higher it is, the lower RPM has to be to reach 1 reading.
// 3rd value is the period value when we are going to have 10 readings. The higher it is, the lower RPM has to be to reach 10 readings.
// 4th and 5th values are the amount of readings range.
RemapedAmountOfReadings = constrain(RemapedAmountOfReadings, 1, 10); // Constrain the value so it doesn't go below or above the limits.
AmountOfReadings = RemapedAmountOfReadings; // Set amount of readings as the remaped value.
}
else
{
PulseCounter++; // Increase the counter for amount of readings by 1.
PeriodSum = PeriodSum + PeriodBetweenPulses; // Add the periods so later we can average.
}
} // End of Pulse_Event.
i got a set of the hall effect modules off amazon which has a built in pullup resistor and an led.
FALLING means HIGH to LOW. How is it wired?
Post link to Amazon page.
the hall effect has a high signal whenever it's idle and only goes low when it's activated
Could you patch up the indenting in your code? It is difficult to follow the logic as is. The Arduino/Tools/Autoformat would make the indenting follow the syntax.
The operation of measuring either turns or RPM is basically debouncing an input and counting the pulses and measuring the intervals. I'd separate the reporting from the debouncing and have one debouncing function that keeps pulse
and PeriodBetweenPulses
up to date, then use those as inputs to reporting functions for rpm and turns. Heck, both would fit on one 16x2 LCD screen:
ln\col|0123456789012345
0 |RPM: 1234567.123
1 |TURN: 1234567890
The Bounce2/examples/more/bounce_previous_duration/bounce_previous_duration.ino at master · thomasfredericks/Bounce2 · GitHub demo modified to count pulses with:
if ( bounce.rose() ) {
digitalWrite(LED_PIN, HIGH); // TURN ON THE LED
ledHighLastTime = millis();
ledHighInterval = bounce.previousDuration();
pulse++;
}
...would do most of the work. Then it's a call to report()
in loop:
void report(void){
const unsigned long interval = 200;
static unsigned long last = -interval;
if(millis() - last >= interval){
last += interval;
Serial.print("RPM: ");
Serial.println(ledHighInterval != 0 ? 60000.0/ledHighInterval: 1e6,3);
Serial.print("TURN: ");
Serial.println(pulse);
}
}
And bounce.currentDuration()
per Bounce2/examples/more/bounce_current_duration/bounce_current_duration.ino at master · thomasfredericks/Bounce2 · GitHub would give you the data you need to identify a stopped spindle.
void Turns(){
detachInterrupt(digitalPinToInterrupt(2)) ;
The first thing you do in Turns() is to detach the interrupt which is used to find the timing for the rpm code.
What is your maximum RPM?
yeah. You were correct. i just moved the original attach interrupt into the code where it needed to be and it fixed the problem.
the max is about 1500 rpm. and will go down to about 25-30 without having any issues now