How to use interrupts

Hi all.

I've never used interrupts before and have a project that looks like I need to use them. I'm still learning to use arduino.

I have a motor that has an optical sensor that outputs a squarewave.

This is the sequence that I need to write code for.

Move motor clockwise to endstop
Detect when the pulses have stopped.
Move motor anti clockwise to endstop
Count pulses from end to end
Move motor to mid position

It sounds like the motor has an encoder, in which case you may be able use one of the standard encoder libraries.

Post a link to the motor specifications. Note that it is not a good idea to run a brushed DC motor into an endstop.

This is tricky, unless you have another sensor detecting that motor reached its limit

No, he KNOWS it has reached a limit because the encoder stops updating. It is often done exactly this way.

I suspect the FreqCount library is a good choice.

Interrupts are most useful to detect events like rising or falling edges of signals and especially if the signals are not "bouncing" (which may put unnecessary load on the controller). So counting pulses via their edges is a valid task and not to complecated if you take care of the basic requirements of interrupt handling.

If you interrupt on edges you need to identify that no further pulses are detected outside the interrupt routine, probably easiest in loop().

For rotary encoders there are examples around, that could be adopted (just a quick google search, no quality assurance :wink: )

https://bristolwatch.com/arduino/arduino2.htm
https://www.instructables.com/How-to-Use-Rotary-Encoders-and-Interrupts-With-You/

The big question for automatic clockwise/counterclockwise detection is whether your hardware gives a single or two signals with a phase shift between them (scroll down to " Working of Rotary encoder:"):

https://www.electroniclinic.com/encoder-with-arduino-rotary-encoder-absolute-encoder-incremental-optical-encoder-encoder-with-arduino/

Hope that gives you some hints ...

I don't know what your motor driver is so you will have to define those functions on your own.

const byte PulsePin = 2;  // Pin conneccted to encoder

unsigned long PulseCount;
unsigned long Midpoint;

// Timeout value looking for an encoder pulse
const unsigned MaxPulseLength = 1000;

void Clockwise();  // Run the motor clockwise
void Anticlockwise();  // Run the motor anticlockwise
void Stop();  // Stop the motor

void CountPulsesUntilTheyStop()
{
  int previousPulseState = digitalRead(PulsePin);
  unsigned long lastPulseTime = millis();

  while (1)
  {
    int pulseState = digitalRead(PulsePin);

    if (pulseState != previousPulseState)
    {
      // State change
      previousPulseState = pulseState;
      PulseCount++;
      lastPulseTime = millis();
    }

    if (millis() - lastPulseTime >= MaxPulseLength)
    {
      // No pulse state change for a while.  Must have hit a stop
      return;
    }
  }
}

void WaitUntilPulseCount(unsigned long count)
{
  int previousPulseState = digitalRead(PulsePin);

  while (1)
  {
    int pulseState = digitalRead(PulsePin);

    if (pulseState != previousPulseState)
    {
      // State change
      previousPulseState = pulseState;
      PulseCount++;
      if (PulseCount >= count)
        return;
    }
  }
}

void MoveToCenter()
{
  // Move motor clockwise
  Clockwise();

  // to endstop
  // Detect when the pulses have stopped.
  CountPulsesUntilTheyStop();
  Stop();

  // Move motor anti clockwise to endstop
  // Count pulses from end to end
  PulseCount = 0;
  Anticlockwise();
  CountPulsesUntilTheyStop();
  Stop();
  Midpoint = PulseCount / 2;

  // Move motor to mid position
  Clockwise();
  PulseCount = 0;
  WaitUntilPulseCount(Midpoint);
  Stop();
}

Warning: The motor is likely to overshoot so this is not a good way to hit a particular target and will be off a little each time you move. If you need to keep track of an accurate position and reach specific position, change the encoder to a quadrature encoder (two channels that overlap) and use a PID control loop to slow the motor before it overshoots.

There is no need to detect rotation direction, if all he is trying to accomplish is what he described. Each phase of the cycle will always begin with the motor stopped. So, he has to turn it on in a given direction. He knows which direction he has turned on. From there, he needs to only count pulses until the motor stops turning again, then turn the motor off, and proceed to the next phase of the cycle, or end the cycle. Quadrature is not required, only pulse counting. He knows the motor is stopped when no new pulses are received within some reasonable period of time, like 10X the average period of the most recent 10 pulses.

How exactly do you know pulses stopped for sure? you have timeout after which you can say they stopped? what if the motor just slowed down? if you extend timeout then you introduce delay in response, so unless you have another sensor telling you the limit is reached you can’t be sure the motor is stopped or rotating slowly

Yes, exactly. How long you wait is determined by the performance/behavior of the hardware, which we know nothing about. In the absence of an explicit hardware limit switch, how else CAN you do it?

What would make it slow down, rather than stop? The description sounds like it will move until it hits an end stop.

The description is very vague I agree, OP needs to describe hardware in details

The motor is a 12v dc motor attached to a gearbox that has a reduction ratio of 1125:1

The sensor on the motor has one IR LED and a IR RECEIVER.

The LED has 0v direct to it and 5v (thru a 150R resistor)

The IR RECEIVER has 5v direct and the output is pulled to 0v via a 10k resistor.

I've got the motor attached to a L298N board which is controlled by a UNO.

Are you absolutely sure the motor and gearbox in that motor will tolerate being stalled? In many of those things, the motor will VERY quickly overheat and burn itself out, and/or the gearbox will break. In any case, with 1125:1 reduction, you can probably ignore any risk of the motor simply slowing down.

That is why I wrote "for automatic clockwise/counterclockwise detection"; if you have a priori knowledge, you can use that of course.

Yes, but - as I wrote - has to be detected outside an event driven interrupt (as no pulses == no events :wink: ) One can use loop() just count the loops, use millis() or microseconds() or create a timer routine reset by each incoming pulse ...

If you take the 10 pulses period as you mentioned as the limit for detection this would be the timing depending on the RPM of the motor when each single motor axis rotation is detected:

image

Depending on the RPM you can choose how long it may take to switch off.

Do you agree?

Oops. I didn't notice that the desire was for the pulse counter to use interrupts. Here is the corrected code.

const byte PulsePin = 2;  // Pin conneccted to encoder

unsigned long PulseCount;
unsigned long Midpoint;

unsigned long LastPulseTime = 0;

// Timeout value looking for an encoder pulse
const unsigned MaxPulseLength = 1000;

// Fill these in to control the motor
void Clockwise() {}  // Run the motor clockwise
void Anticlockwise() {}  // Run the motor anticlockwise
void Stop(){}  // Stop the motor

void PulseISR()
{
  PulseCount++;
  LastPulseTime = millis();
}

void setup()
{
  attachInterrupt(digitalPinToInterrupt(PulsePin), PulseISR, CHANGE);

  MoveToCenter();
}

void CountPulsesUntilTheyStop()
{
  noInterrupts();
  LastPulseTime = millis();
  interrupts();

  while (1)
  {
    noInterrupts();
    unsigned long LPT = LastPulseTime;
    interrupts();
    if (millis() - LPT >= MaxPulseLength)
    {
      // No pulse state change for a while.  Must have hit a stop
      return;
    }
  }
}

void WaitUntilPulseCount(unsigned long count)
{
  while (1)
  {
    noInterrupts();
    unsigned long PC = PulseCount;
    interrupts();

    if (PC >= count)
      return;
  }
}

void MoveToCenter()
{
  // Move motor clockwise
  Clockwise();

  // to endstop
  // Detect when the pulses have stopped.
  CountPulsesUntilTheyStop();
  Stop();

  // Move motor anti clockwise to endstop
  // Count pulses from end to end
  PulseCount = 0;
  Anticlockwise();
  CountPulsesUntilTheyStop();
  Stop();
  Midpoint = PulseCount / 2;

  // Move motor to mid position
  Clockwise();
  PulseCount = 0;
  WaitUntilPulseCount(Midpoint);
  Stop();
}

void loop() {}

Sure. But, again, we know almost NOTHING about the OPs actual hardware, nor his performance requirements. And, he may well get the performance he needs by simply waiting for a SINGLE pulse that exceeds perhaps 2X the "full-speed" pulse rate. And that is probably about as fast as it CAN realistically be detected. We simply do not KNOW what he needs, as he has not told us.

IOW - Another X/Y problem...

1 Like

Johnwasser thank you for the 2 codes. I've looked at the one with interrupts and added the relevant code to control the motor. It moves the motor clockwise for a second then anti clockwise for a second so I'm thinking i have a problem with the pulses coming from the motor.

Will have a look with the scope and see whats going on.

Ive attached what the sensor is outputing with the motor at full speed.

johnwasser the first program without interrupt will not complie for my nano. The second program with interrupt does not count the pulses. Ive added Serial.print to show plusecount and midpoint and both show 0.

Here is the code as I have it

const byte PulsePin = 2;  // Pin conneccted to encoder

const int in1 = 6;
const int in2 = 7;

unsigned long PulseCount;
unsigned long Midpoint;

unsigned long LastPulseTime = 0;

// Timeout value looking for an encoder pulse
const unsigned MaxPulseLength = 20;

// Fill these in to control the motor
void Clockwise() {

  digitalWrite(in1, HIGH); digitalWrite(in2, LOW);
}  // Run the motor clockwise
void Anticlockwise() {

  digitalWrite(in1, LOW); digitalWrite(in2, HIGH);
}  // Run the motor anticlockwise
void Stop() {

  digitalWrite(in1, LOW); digitalWrite(in2, LOW);
}  // Stop the motor

void PulseISR()
{
  PulseCount++;
  LastPulseTime = millis();
}

void setup()
{

  Serial.begin (9600);
  
  pinMode(in1, OUTPUT);        pinMode(in2, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(PulsePin), PulseISR, CHANGE);

  MoveToCenter();
}

void CountPulsesUntilTheyStop()
{
  noInterrupts();
  LastPulseTime = millis();
  interrupts();

  while (1)
  {
    noInterrupts();
    unsigned long LPT = LastPulseTime;
    interrupts();
    if (millis() - LPT >= MaxPulseLength)
    {
      // No pulse state change for a while.  Must have hit a stop
      return;
    }
  }
}

void WaitUntilPulseCount(unsigned long count)
{
  while (1)
  {
    noInterrupts();
    unsigned long PC = PulseCount;
    interrupts();

    if (PC >= count)
      return;
  }
}

void MoveToCenter()
{
  // Move motor clockwise
  Clockwise();

  // to endstop
  // Detect when the pulses have stopped.
  CountPulsesUntilTheyStop();
  Stop();

  // Move motor anti clockwise to endstop
  // Count pulses from end to end
  PulseCount = 0;
  Anticlockwise();
  CountPulsesUntilTheyStop();
  Stop();
  Midpoint = PulseCount / 2;
  Serial.print(PulseCount);
  Serial.print("\t");
  Serial.println();
  Serial.print(Midpoint);
  Serial.print("\t");

  // Move motor to mid position
  Clockwise();
  PulseCount = 0;
  WaitUntilPulseCount(Midpoint);
  Stop();
}

void loop() {}

...is not only unnecessary but can be catastrophic.