Having trouble using Interrupt and Hall Effect Switch as a Counter

Before I begin describing my problem - the sketch is below and the schematic is attached.
I am using a small neodimium magnet glued to the side of an electric drill DC motor and a TLE-4905 Unipolar Hall Effect Switch as the sensor. The TLE-4905 is an open-collector device with a 4k7 pull-up.

The open-collector output of the TLE-4905 is connected to digital input 2 and is used as an interrupt to call a function on the rising edge that decrements volatile integer “count”. “Count” is then printed so can follow it in the Serial Monitor.

The sketch runs as long as “counter” is greater than 0.

The problem I have having is that according to the Serial Monitor “count” is decremented more than once on a single pulse quite often. This is a problem since I need the counter to be accurate.

I have tried several different baud rates but still does not work.

What am I doing wrong?

/*
  Coil Winder sketch
  Potentiometer to control speed.
  TLE4095 Hall Effect Sensor as a counter.
  MegaMoto shield used to drive a DC Motor.
*/

int EnablePin = 8; 
int duty;
int PWMPin = 11;    // Timer2
int PWMPin2 = 3;
int potPin = A0;    
int potValue = 0;
volatile int count;
/*
const byte CPin = 0;  // analog input channel
int CRaw;      // raw A/D value
float CVal;    // adjusted Amps value
*/
void setup()
{              
  // initialize the digital pin as an output.
  // Pin 13 has an LED connected on most Arduino boards:
  count = 700;
  pinMode(EnablePin, OUTPUT);   
  pinMode(PWMPin, OUTPUT);   
  pinMode(PWMPin2, OUTPUT);  
  setPwmFrequency(PWMPin, 8);  //change Timer2 divisor to 8 gives 3.9kHz PWM freq
  Serial.begin(2400);
  attachInterrupt(0, magnet_detect, RISING);//Initialize the interrupt pin (Arduino Digital pin 2)
}
void loop()
{
    // To drive the motor in H-bridge mode
    // the power chip inputs must be opposite polarity
    // and the Enable input must be HIGH
    
      if(count>0)
      {
        digitalWrite(EnablePin, HIGH);    // Pin 8 high, enable motor driver
      }
      else
      {
        digitalWrite(EnablePin, LOW);    // Pin 8 low, disable motor driver
      }
        analogWrite(PWMPin2, 0);          // Set Pin 3 low 
        potValue = analogRead(potPin);    // Read voltage from pot wiper
        duty = map(potValue, 0, 663, 5, 255);
        analogWrite (PWMPin, duty); 
}
void magnet_detect() //This function is called whenever a magnet/interrupt is detected
{
  --count;
  Serial.print("\n count = ");
  Serial.print(count);
}
        
/*
 * Divides a given PWM pin frequency by a divisor.
 * 
 * The resulting frequency is equal to the base frequency divided by
 * the given divisor:
 *   - Base frequencies:
 *      o The base frequency for pins 3, 9, 10, and 11 is 31250 Hz.
 *      o The base frequency for pins 5 and 6 is 62500 Hz.
 *   - Divisors:
 *      o The divisors available on pins 5, 6, 9 and 10 are: 1, 8, 64,
 *        256, and 1024.
 *      o The divisors available on pins 3 and 11 are: 1, 8, 32, 64,
 *        128, 256, and 1024.
 * 
 * PWM frequencies are tied together in pairs of pins. If one in a
 * pair is changed, the other is also changed to match:
 *   - Pins 5 and 6 are paired (Timer0)
 *   - Pins 9 and 10 are paired (Timer1)
 *   - Pins 3 and 11 are paired (Timer2)
 * 
 * Note that this function will have side effects on anything else
 * that uses timers:
 *   - Changes on pins 5, 6 may cause the delay() and
 *     millis() functions to stop working. Other timing-related
 *     functions may also be affected.
 *   - Changes on pins 9 or 10 will cause the Servo library to function
 *     incorrectly.
 * 
 * Thanks to macegr of the Arduino forums for his documentation of the
 * PWM frequency divisors. His post can be viewed at:
 *   http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1235060559/0#4
 */

void setPwmFrequency(int pin, int divisor) {
  byte mode;
  if(pin == 5 || pin == 6 || pin == 9 || pin == 10) { // Timer0 or Timer1
    switch(divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 64: mode = 0x03; break;
      case 256: mode = 0x04; break;
      case 1024: mode = 0x05; break;
      default: return;
    }
    if(pin == 5 || pin == 6) { 
      TCCR0B = TCCR0B & 0b11111000 | mode; // Timer0
    } else {
      TCCR1B = TCCR1B & 0b11111000 | mode; // Timer1
    }
  } else if(pin == 3 || pin == 11) {
    switch(divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 32: mode = 0x03; break;
      case 64: mode = 0x04; break;
      case 128: mode = 0x05; break;
      case 256: mode = 0x06; break;
      case 1024: mode = 0x7; break;
      default: return;
    }
    TCCR2B = TCCR2B & 0b11111000 | mode; // Timer2
  }
}

CoilWinder.pdf (26.4 KB)

Not sure if it would cause the problem you are seeing, but Serial should not be used inside an interrupt service routine. You can set a flag in the ISR to indicate when count has changed, or just check for a change in the value of count, to know when to print to serial.
Also, interrupts need to be disabled when accessing the count variable outside of the ISR, to prevent an interrupt from occurring between accessing the first and 2nd byte of the integer (the UNO uses an 8-bit processor, and has to access an int variable one byte at a time, allowing interrupts to occur and change the value between bytes).

Then it looks like I am doing a couple things wrong...

Thank you for taking time to reply. I need to re-evaluate my approach. If you have any pointers I would love to hear it.

Thanks again.

Here is a revision of your sketch with modifications which incorporate @david_2018's suggestions as well as putting the analogReads on a periodic timer to reduce potential interaction with the count interrupts. I have also change the Serial baud rate for faster printing. Try the interrupt with both RISING and FALLING modes, as sometimes the signal from one edge is cleaner than the other.

int EnablePin = 8;
int duty = 5; //default duty cycle, in initialize at 5
int PWMPin = 11;    // Timer2
int PWMPin2 = 3;
int potPin = A0;
int potValue = 0;
volatile int count;
int copyCount;
int lastCopyCount;
unsigned long lastAnalogReadTime = millis();
unsigned long readInterval = 1000;

void setup()
{
  count = 700;
  pinMode(EnablePin, OUTPUT);
  pinMode(PWMPin, OUTPUT);
  pinMode(PWMPin2, OUTPUT);
  setPwmFrequency(PWMPin, 8);  //change Timer2 divisor to 8 gives 3.9kHz PWM freq
  //Serial.begin(2400);
  Serial.begin(115200);
  //try both RISING and FALLING modes to see if one gives a cleaner signal
  attachInterrupt(0, magnet_detect, RISING);//Initialize the interrupt pin (Arduino Digital pin 2)
  //attachInterrupt(0, magnet_detect, FALLING);//Initialize the interrupt pin (Arduino Digital pin 2)
}
void loop()
{
  //make a copy of count value when it can not be changing
  noInterrupts();
  copyCount = count;
  interrupts();

//print count value if it has changed
  if (copyCount != lastCopyCount)
    Serial.println(copyCount);

  lastCopyCount = copyCount;

  if (copyCount > 0)
  {
    digitalWrite(EnablePin, HIGH);    // Pin 8 high, enable motor driver
  }
  else
  {
    digitalWrite(EnablePin, LOW);    // Pin 8 low, disable motor driver
  }

  //periodic analogRead()for speed change
  if (millis() - lastAnalogReadTime >= readInterval)
  {
    lastAnalogReadTime = millis();
    potValue = analogRead(potPin);    // Read voltage from pot wiper
    duty = map(potValue, 0, 663, 5, 255);
    analogWrite(PWMPin2, 0);          // Set Pin 3 low
    analogWrite (PWMPin, duty);
  }
}
void magnet_detect() //This function is called whenever a magnet/interrupt is detected
{
  --count;
  //Serial.print("\n count = ");
  //Serial.print(count);
}

void setPwmFrequency(int pin, int divisor) {
  byte mode;
  if (pin == 5 || pin == 6 || pin == 9 || pin == 10) { // Timer0 or Timer1
    switch (divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 64: mode = 0x03; break;
      case 256: mode = 0x04; break;
      case 1024: mode = 0x05; break;
      default: return;
    }
    if (pin == 5 || pin == 6) {
      TCCR0B = TCCR0B & 0b11111000 | mode; // Timer0
    } else {
      TCCR1B = TCCR1B & 0b11111000 | mode; // Timer1
    }
  } else if (pin == 3 || pin == 11) {
    switch (divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 32: mode = 0x03; break;
      case 64: mode = 0x04; break;
      case 128: mode = 0x05; break;
      case 256: mode = 0x06; break;
      case 1024: mode = 0x7; break;
      default: return;
    }
    TCCR2B = TCCR2B & 0b11111000 | mode; // Timer2
  }
}

Wow. Thank you so much for taking the time to do that.

I will give it a try as soon as I can.

I was playing with the baud rate because it seemed like that was helping. There is so much about this that I don't understand yet.

cattledog: Here is a revision of your sketch with modifications which incorporate @david_2018's suggestions as well as putting the analogReads on a periodic timer to reduce potential interaction with the count interrupts. I have also change the Serial baud rate for faster printing. Try the interrupt with both RISING and FALLING modes, as sometimes the signal from one edge is cleaner than the other.

I spent some time going over the revisions yesterday and I was able to run it tonight with my coil winder. Now that I have run it I need to review the sketch again.

There are a couple things though: I do feel more confident in the counter but there are some peculiarities in the way it shows up in the serial monitor. Occasionally, a couple of of "counts" will show up with the time stamp. Less frequently, the "count" will skip a number (example 7, 8, 10,...) I suspect this is more to do with the Serial Monitor.

The other issue is with the motor speed. I am using a pot for control and the PWM output to drive the DC motor with a Motor Shield. As I rotate the pot to increase or reduce motor speed it is very jerkie (not gradual).When left at a constant setting the motor will not stay at a constant speed. It is slight, but the motor will speed and slow down as it runs.

I will review the sketch with these things in mind. I would really like to address the motor speed control.

When left at a constant setting the motor will not stay at a constant speed. It is slight, but the motor will speed and slow down as it runs.

As I rotate the pot to increase or reduce motor speed it is very jerkie (not gradual).

How is the electric drill motor being powered?

If you comment out the analogRead() section mapped to the pwm and instead set a fixed analogWrite() value in set up, does the motor run at a constant speed?

You can also add a Serial print to the analogRead() section to see how the values are varying when the setting is constant. It may be necessary to put a dead band around a setting, and only change the AnalogWrite() value when the pot reading changes by more than a threshold value. It should then be possible to add a ramp up or down between values.

If the motor is very sensitive to the pwm values over a small range, it may be possible to change the speed control approach and perhaps use a toggle switch to control faster or slower, and then a multiple button arrangement which only changes the pwm value by fixed amounts. A ten turn pot may also be a possibility.

Run the program for awhile, and get a feel for the performance, and the issues with speed control.

I do feel more confident in the counter but there are some peculiarities in the way it shows up in the serial monitor. Occasionally, a couple of of "counts" will show up with the time stamp. Less frequently, the "count" will skip a number (example 7, 8, 10,...) I suspect this is more to do with the Serial Monitor.

Regarding the counts, can you run the motor slow enough to visually verify the count? Is there a slow running speed which performs reliably and only appears to increase the count by one at each serial monitor time stamp value?

Skipping a number will be normal if the motor is running fast, and two interrupts occur in the time period it takes to run through the loop().

What is the rpm/rps of the winder, and how many interrupts do you expect in a period of time?

Hi,
Here is the OPs circuit, it would be better if you got your CAD to EXPORT an image in jpg to get better resolution, rather than post in pdf.

Tom… :slight_smile:

cattledog: How is the electric drill motor being powered?

If you comment out the analogRead() section mapped to the pwm and instead set a fixed analogWrite() value in set up, does the motor run at a constant speed?

You can also add a Serial print to the analogRead() section to see how the values are varying when the setting is constant. It may be necessary to put a dead band around a setting, and only change the AnalogWrite() value when the pot reading changes by more than a threshold value. It should then be possible to add a ramp up or down between values.

If the motor is very sensitive to the pwm values over a small range, it may be possible to change the speed control approach and perhaps use a toggle switch to control faster or slower, and then a multiple button arrangement which only changes the pwm value by fixed amounts. A ten turn pot may also be a possibility.

Run the program for awhile, and get a feel for the performance, and the issues with speed control.

Regarding the counts, can you run the motor slow enough to visually verify the count? Is there a slow running speed which performs reliably and only appears to increase the count by one at each serial monitor time stamp value?

Skipping a number will be normal if the motor is running fast, and two interrupts occur in the time period it takes to run through the loop().

What is the rpm/rps of the winder, and how many interrupts do you expect in a period of time?

It powered with a 18V cordless drill battery via a motor shield. The motor is from an old drill that is rated at 600RPM with the 14.4V battery it came with. It is not a fast motor, which is fine with me. I will do as you suggested add a Serial Print to the analogRead() section and run the program for a while to get a fell for the performance. For what it is worth, I did not have these issues with motor control in the previous version. Was there something wrong with the way the motor control was handled in the previous version?

When I ran the program last night I purposefully ran the motor slow so I could watch the count. It didn't matter if it was run fast or slow. It also didn't seem like it happened more or less often at slow or fast speed. I was hoping you were going to tell me that is just peculiarities with the Serial Print but it is starting to sound more like issues with the sensor.

I need to spend some more time with it and troubleshoot the sketch and hardware.

I have looked at the sensor output on my scope but is it an older, cheap digital scope and I may not be getting all the information. I am using a small neodymium magnet glued on to the side of the chuck to trigger the hall effect switch. Maybe the magnet needs to pass over the sensor in a linear plane rather than rotational. I did use a recommended circuit on the TLE4905 datasheet but maybe I need to add a small series resistor. Maybe I need to abandon the hall effect sensor altogether in favor of an optical.

I admit, this is a pretty crude winder. I always had intentions of moving to a stepper motor.

Hi, Can you post a picture of your project please? In particular the hall-effect /magnet area.

Thanks.. Tom... :)

Sorry it took so long.
Pics are attached.

I have not had any time to spend with it this weekend.

Hi, OPs pics. |500x375 |500x375

Tom.... :)

Quick update -

I inserted a 33R resistor series with the hall effect switch output and D02.
This helped quite a bit. There were far fewer instances of same time time stamps with sequential “count” and there were no skipped "count"s.

I do need to work on the motor control. The jump in speed during gradual rotation is too dramatic. There does seem to a certain spot in rotation that the speed jump quite a bit. The jump is speed is reflected in printed value of “potValue”. It is a linear pot. Also, slight changes in speed while pot is left alone is also shown in printed “potValue”

I inserted a 33R resistor series with the hall effect switch output and D02. This helped quite a bit. There were far fewer instances of same time time stamps with sequential "count" and there were no skipped "count"s.

Your circuit shows a .1uf capacitor in parallel to the added resistor, and this low pass filter will help if you are getting short spikes from the sensor. At 600 rpm max there are 100 ms between pulses.(10 per second max).

An online low pass filter design tool will help you optimize http://sim.okawa-denshi.jp/en/CRtool.php A very simple change would be to up the resistor to 330 ohms.

You can also put a lockout time between interrupts in the isr for a software approach. Experiment with this.

void magnet_detect()
{
  const unsigned long lockout = 50;//50ms
  static unsigned long last_interrupt_time = 0;
  unsigned long interrupt_time = millis();
  if (interrupt_time - last_interrupt_time > lockout)
  {
    count--;
  }
  last_interrupt_time = interrupt_time;
}

It's still unclear to me why you are getting a noisy response from your sensor. Do you know what the magnet polarity facing the sensor is? The data sheet for the TLE4095L shows the south pole of the magnet facing the branded side of the sensor.

I will have to confirm magnet pole, I don’t remember.
If the datasheet says south-pole toward the sensor then that is how it is assembled. I do remember that one pole triggered the sensor, the other pole did not.

The 2 issues I am having with this sensor are that there are two sequential “counts” with the same timestamp, and then a “count” getting skipped, ie. 10, 9, 7, 6, etc. (8 getting skipped). It doesn’t seem to me like this is a noisy sensor, more like there is a bit of chatter on rising/falling edges. (Yes, I did try both).
A series resistor up to 330R like you suggested should be adequate, right?

I will confirm magnet pole.

If the datasheet says south-pole toward the sensor then that is how it is assembled. I do remember that one pole triggered the sensor, the other pole did not.

This would indicate that you have the polarity correct.

The 2 issues I am having with this sensor are that there are two sequential "counts" with the same timestamp, and then a "count" getting skipped, ie. 10, 9, 7, 6, etc. (8 getting skipped).

Can you please show some actual output from the monitor with the time stamps and counts?

The important question is does the count reflect the actual rotations. It's quite possible that the interrupt picks up two rotations before the program gets around to printing the output. In that case, there will be a skipped count in the print, but the count is accurate.

If you try the "lock out" isr it may also provide a clue as to whether you are seeing false counts or actual ones.

Ok. I’ll try the lockout ISR as soon as I can.

I was hoping that this was print peculiarities.
Adding a 33R in series seems like it helped quite a bit though. I should point out that this 33R is NOT between sensor output and 0.1uF. It is between D02 and the 4k7/0.1uF.

Adding a 33R in series seems like it helped quite a bit though. I should point out that this 33R is NOT between sensor output and 0.1uF. It is between D02 and the 4k7/0.1uF.

I'm not very knowledgeable about hardware, and I'm not certain about the placement of the resistor being before or after the capacitor as long as the cap is going to ground and the resistor is in series to the input.

I was hoping that this was print peculiarities.

Very possible. But, if you can see this when rotating slowly like 60 rpm (1 rev/sec) I doubt you are getting two actual counts between prints and the output prints should be incrementing by one.

cattledog: I'm not very knowledgeable about hardware, and I'm not certain about the placement of the resistor being before or after the capacitor as long as the cap is going to ground and the resistor is in series to the input.

I'm the opposite. My experience is in hardware, specifically more analog. Recently I have getting more exposure to MCU's and converters and this is where I have seen small series R's in series with digital inputs. The explanation given to me is that it helps to handle false triggers.

cattledog: Very possible. But, if you can see this when rotating slowly like 60 rpm (1 rev/sec) I doubt you are getting two actual counts between prints and the output prints should be incrementing by one.

Agreed. To me, it is more likely that there is some chatter on rising/falling edges (what you referred to as noise?).