Hello!
I am trying to create a hall effect sensor driven RPM detector using an ATtiny85.
The problem I am having is that the interrupt is being triggered on any change (rising or falling) and is causing the RPM reading to be wildly inaccurate and unpredictable. Instead of just triggering an interrupt any time the magnetic field changes, I want to be able to detect a rising edge and then a falling edge to determine that a full rotation has occurred instead of detecting a possible dozen triggers during one single rotation of a two-pole rotor due to magnetic field strength variations between each trigger sample period.
This is the code I am using:
//Includes
#include <avr/interrupt.h>
//Initialize variables
unsigned int timer[2] = {millis(), millis()};
unsigned int rpm[5] = {0, 0, 0, 0, 0};
unsigned int record = 0;
unsigned int idle[3] = {millis(), millis(), 0};
const int poles = 2;
void setup()
{
pinMode(0, OUTPUT);
pinMode(1, OUTPUT);
pinMode(2, OUTPUT);
pinMode(3, OUTPUT);
pinMode(4, INPUT);
GIMSK = 0b00100000; // turns on pin change interrupts
PCMSK = 0b00010000; // turn on interrupts on pins PB4
sei(); // enables interrupts
digitalWrite(0, LOW);
digitalWrite(1, LOW);
digitalWrite(2, LOW);
digitalWrite(3, LOW);
}
void loop()
{
// display values using LEDs
if(rpm[0] > 0 && rpm[0] < 6500)
{
digitalWrite(0, HIGH);
digitalWrite(1, LOW);
digitalWrite(2, LOW);
digitalWrite(3, LOW);
}
// ...
// more multiplexing of LEDS in here based on RPM reading
// ...
else if(rpm[0] >= 9000)
{
digitalWrite(0, HIGH);
digitalWrite(1, LOW);
digitalWrite(2, LOW);
digitalWrite(3, HIGH);
}
// keep track of max RPM record this session
if(rpm[0] > record)
{
record = rpm[0];
}
// save current active time
idle[0] = millis();
// if inactive for 5 seconds, show record RPM
if((idle[0] - idle[1]) > 5000 && idle[2] == 0)
{
// reset RPM readings for next run
rpm[0] = 0;
rpm[1] = 0;
rpm[2] = 0;
rpm[3] = 0;
rpm[4] = 0;
// keep track of idle status
idle[1] = millis();
idle[2] = 1;
// display values using LEDs
if(record > 0 && record < 6500)
{
digitalWrite(0, HIGH);
digitalWrite(1, LOW);
digitalWrite(2, LOW);
digitalWrite(3, LOW);
}
// ...
// more multiplexing of LEDS in here based on RPM stored record
// ...
else if(record >= 9000)
{
digitalWrite(0, HIGH);
digitalWrite(1, LOW);
digitalWrite(2, LOW);
digitalWrite(3, HIGH);
}
}
}
ISR(PCINT0_vect)
{
// store the old RPM readings
rpm[4] = rpm[3];
rpm[3] = rpm[2];
rpm[2] = rpm[1];
// get the current time
timer[0] = millis();
// compare the times to determine the RPM taking into consider the number of poles per rotation
rpm[1] = (60000 / (timer[0] - timer[1])) / poles;
// save the current time for the next loop to compare against
timer[1] = timer[0];
// average the last 4 RPM readings
rpm[0] = (rpm[1] + rpm[2] + rpm[3] + rpm[4]) / 4;
// save current active time and state
idle[1] = millis();
idle[2] = 0;
}
Attached is a circuit schematic drawing and an oscilloscope image of the hall effect output signal from one rotation of the rotor.
The input to Pin3 (PB4) of ATtiny85 comes from a DRV5053 hall effect sensor with an at-rest voltage output of approximately 1.0 volts which rises up to a 1.8 volt peak and falls down to a 0.2 volt dip.
I have had some success by instead using an analogRead in the void loop and manually keeping track of when a value is rising, has peaked in the positive direction, is falling, has peaked in the falling direction, etc., but that seems like a pretty inelegant solution and I would rather use an interrupt.
Thank you for the help!
Let me know if you need more details!