Hi there!
Im currently working on a project where I need a rotary encoder and an RPM sensor. Both of them need interrupts so they need to be 3 interrupts but the Arduino UNO only supports 2.
Any ideas for a workaround?
Best and thanks in advance!
Hi there!
Im currently working on a project where I need a rotary encoder and an RPM sensor. Both of them need interrupts so they need to be 3 interrupts but the Arduino UNO only supports 2.
Any ideas for a workaround?
Best and thanks in advance!
The Uno supports way more than two interrupts. That's just the two "external interrupts". All pins have pin change interrupts, for starters.
Thanks for your reply!
I didnt know that!
So I can just use attachInterupt with "CHANGE" to any digital pin of the UNO?
I think the encoder library uses "CHANGE" but for the RPM I would need "RISING" or "FALLING" so these have to be on digital pin 2 or 3 right?
No, pin change interrupt is a different thing than regular interrupt. Tutorial here.
Two major differences:
Or you could use a Leonardo, Micro or other board that uses the ATMega 32u4 processor. That processor has 5 external interrupts, or an ATMega 2560 board, such as the Arduino Mega which has 6.
Review the Arduino reference page for attachInterrupt().
Come to think of it, this is one of the few things where the ESP8266 beats even the ATmega2560 - it has external interrupts for all pins except GPIO16 and the analog pin, so 10 total.
Pin change interrupts will do very nicely for the encoder. Just make sure they're on the same port, and when either of the pins changes value your ISR gets called. That one ISR can then take care of both pins, may make your code a bit simpler even.
Use one of your external interrupts for the RPM sensor.
Connect two of your lines via diodes to one interrupt pin - then either of the two devices will generate an interrupt. Upstream of one Diode connect that to another digital pin - then when an interrupt occurs , in your service routine you can check which device caused it by looking at the other digital pin .
Use the Encoder library for the encoder. It does not require either pin to be interrupt pins. It does work a little better if one pin is an interrupt pin.
Won't work.
If you're suggesting to connect both the RPM and encoder to one interrupt pin, it's even worse:
3) when the encoder pin is low, you can't read RPM any more.
4) while the RPM signal is in its low state, you can't read the encoder.
(depending on the direction of the diodes the high/low may be reversed, this doesn't solve any of these issues).
Multiplexing pins can sometimes be done, but you have to be really careful about what you're doing on the same pin. On an ATtiny85 project I ran out of pins, needed an extra one, in the end used one pin as both a digital output and analog input. Works like a charm, but only because both parts are fully tolerant of the other: the digital output is for the EC pin of my EC sensor, the analog in on the same pin is an NTC. It's so far the only time I successfully multiplexed a single pin to handle two sensors.
Yep fair enough , you do need the pull up.
You are right I hadn’t considered the case of two simulatanious interrupts which might both be valid .
Ho
Hum
Lol
Thanks guys for all the input you gave me.
I decided to go this route:
Im going to setup and use pin 9 like this:
volatile int rpmCounter = 0;
int rpm = 0;
unsigned long lastMillis = 0;
void setup()
{
interruptPin(9);
}
void loop()
{
if (millis() - lastMillis == 1000)
{
rpm = rpmCounter * 30;
rpmCounter = 0;
lastMillis = millis();
}
}
void interruptPin(byte pin)
{
*digitalPinToPCMSK(pin) |= bit (digitalPinToPCMSKbit(pin)); // enable pin
PCIFR |= bit (digitalPinToPCICRbit(pin)); // clear any outstanding interrupt
PCICR |= bit (digitalPinToPCICRbit(pin)); // enable interrupt for the group
}
ISR (PCINT0_vect) // handle pin change interrupt for D8 to D13 here
{
rpmCounter++;
}
Is this correct?!
Is this correct?!
You realize you are not counting RPMs, right? You are counting pulses, which may, or may not, correspond directly to revolutions. So, rpmCounter is a lousy name.
You are also counting every CHANGE, not every RISING edge or every FALLING edge. So, your reading is likely to be double what you expect.
Finally, you could get an interrupt between setting the low order byte of the poorly named rpmCounter and setting the high order byte. Before resetting rpmCounter, you should disable interrupts, and re-enable them after resetting rpmCounter.
Thanks!
You realize you are not counting RPMs, right? You are counting pulses, which may, or may not, correspond directly to revolutions. So, rpmCounter is a lousy name.
Im going to rename rpmCounter to rpmSensorPulses
PaulS:
Before resetting rpmCounter, you should disable interrupts, and re-enable them after resetting rpmCounter.
The problem is that interrupts need to be enabled because I need to read the encoder at the same time. Is it possible to just temporaliy disable the interrupts for PORTD?
The problem is that interrupts need to be enabled because I need to read the encoder at the same time.
How long do you think it will take to set the high order byte of the int to 0 and then set the low order byte to 0?
In other words, just how long will interrupts need to be disabled?
How long do you think it will take to set the high order byte of the int to 0 and then set the low order byte to 0?
Not very long maybe?!
So would this be more correct?
volatile int rpmSensorPulses = 0;
int rpm = 0;
unsigned long lastMillis = 0;
void setup()
{
interruptPin(9);
}
void loop()
{
if (millis() - lastMillis == 1000)
{
rpm = rpmSensorPulses * 30;
noInterrupts();
rpmSensorPulses = 0;
interrupts();
lastMillis = millis();
}
}
void interruptPin(byte pin)
{
*digitalPinToPCMSK(pin) |= bit (digitalPinToPCMSKbit(pin)); // enable pin
PCIFR |= bit (digitalPinToPCICRbit(pin)); // clear any outstanding interrupt
PCICR |= bit (digitalPinToPCICRbit(pin)); // enable interrupt for the group
}
ISR (PCINT0_vect) // handle pin change interrupt for D8 to D13 here
{
rpmSensorPulses ++;
}
if (millis() - lastMillis == 1000)
This is dangerous. It'll go fine in your code, but what if you're doing something that takes longer than 1 ms and happens to start at 999 ms? Then you miss your moment in time, and it won't trigger for at least another 49 days! So you have to trigger when it's > 1000 and then can use the actual time that passed to correct your rpm.
void loop()
{
if (millis() - lastMillis > 1000)
{
unsigned int dTime = millis() - lastMillis; // no need for a long here: it won't ever be >65535.
noInterrupts(); // Prevent the rpmCounter to be updated while you work on it.
unsigned int rpmCount = rpmCounter; // Copy the counted value
rpmCounter = 0;
interrupts(); // Re-enable interrupts asap.
rpm = rpmCount * 30 * dTime/1000.0; // The .0 forces a floating point calculation here for accuracy.
lastMillis = millis();
}
}
asuryan:
The problem is that interrupts need to be enabled because I need to read the encoder at the same time. Is it possible to just temporaliy disable the interrupts for PORTD?
As far as I know you won't miss interrupts - they're put on hold. That is, unless there's a second pulse within the time you have the interrupts disabled, but in the code that I just posted it's disabled for only a few clock cycles, so <1µs. So no, you won't miss interrupts.
The reason you have to disable it while working on the variable is because it is possible that the interrupt happens halfway the writing of 0 to rpmCounter - a 2-byte value, first one, then the other byte gets written. That takes two cycles. Doing calculations with the value is even worse: if the value changes during such a calculation strange things may happen.
Copying the value into a separate variable and updating rpmCount itself while interrupts are disabled prevents all that. In code as simple as yours with interrupts coming in slowly it may feel like or even be overkill, but it won't ever hurt, and anyway it's good to learning proper programming techniques now, as unlearning bad things later is usually MUCH harder than learning the correct thing now.
Thank you so much for your help.
I implemented the rpm counter as you suggested.
Unfortunately the ISR gets called 8 times for one (1) change event.
Any ideas where this could come from?
Thanks in advance!
With the code I posted: do I need to keep track of the pin state of 9? And flag it? Do all pins from this port fire maybe?
Pin change interrupts are enabled on a per pin basis.
Post your latest code, then maybe can see what's going on. If the ISR gets called multiple times there's probably something wrong with the wiring or the sensor, as that means there are 8 change events (so four pulses). Or your sensor doesn't work the way you think it does.