when i run this code, it is ok with very slow rotation speed, but if i rotate it with little faster, then it return a little error. i thought that error will occurs by interrupt when interrupt. my rotary encoder PPR is 6000.
so i was study about pin change interrupt and i change my code like this.
in this time, i connect the rotary encoder at 5v, gnd and A phase at pin number 8.
i thought that setting Pin Change Interrupt Control Register(PCICR) to 0b00000001 and it means i will use B port to interrupt.
and Pin Change Mask Register 0(PCMSK0) is setted 0b00000001 so it will interrupted when digital pin 8 is changed.
so when i rotate the encoder then phase A will pulsed, and my Serial will show "a" on Serial monitor, but it won't.
is there any problem on my code? or should i compile at atmel studio?(i was compile and upload this code at Arduino IDE.) or my reference data is wrong?
your counter should be volatile and you should not print from an ISR.
Printing uses interrupts and they are disabled within your ISR, that can lead to a lock up if the Serial buffer is full.
also dealing with anything that is not just 1 byte and coming from an interrupt is error prone. Your int is 2 bytes long, one of the byte could be modified whilst trying to print without you knowing it. You should duplicate in the loop — in a interrupt protected section of your code — the variables that might get modified if an interrupt were to come at that moment.
noInterrupts();
// critical, time-sensitive code here
int tmpCnt = _count;
interrupts();
// now you can safely use tmpCnt as you froze its value to whatever it was
that being said, unless this is for exploration and learning, if I were you I would use the excellent encoder library from Paul Stoffregen & co, it's proven to work at pretty high speed.
Yes for println() outside interrupts but I also suggested a critical section.
I'd suggest you get it working without playing with registers, just use the high level construct provided by the Arduino environment - may be something like this based on your first code.
volatile long _count = 0;
const byte encoderPin = 2;
void tick() {_count++;}
void setup()
{
Serial.begin(115200); // *** SET SERIAL CONSOLE TO 115200 BAUDS AS WELL ***
pinMode(encoderPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(encoderPin), tick, CHANGE);
}
void loop()
{
if(Serial.read() != -1) { // -1 means there was nothing to read
noInterrupts();
long tmpCnt = _count; // local copy to be safe
interrupts();
Serial.println(tmpCnt);
}
}
Of course if your button is bouncing — which is very likely — you'll get plenty of ticks per rotation.
adding a cap next to the rotary (between output of rotary and GND) will solve this bouncing.
Rotary encoders I know do have 2 output though - so not sure why you only care about one.
Once you get this working to your satisfaction, you can explore how to set pins and registers to do the right thing if you want to be closer to the silicon.
volatile long _count = 0;
const byte phaseA = 2;
const byte phaseB = 8;
const int ppr = 12000;
const float delta = 360.0/ppr;//12000 is PPR
void tick()
{
if(digitalRead(phaseA) ^ digitalRead(phaseB))
_count++;
else
_count--;
if(_count < 0)
_count += ppr;
else
_count = _count % ppr;
}
void setup()
{
Serial.begin(115200); // *** SET SERIAL CONSOLE TO 115200 BAUDS AS WELL ***
pinMode(phaseA, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(phaseA), tick, CHANGE);
}
void loop()
{
if(Serial.read() != -1) { // -1 means there was nothing to read
noInterrupts();
long tmpCnt = _count; // local copy to be safe
interrupts();
Serial.println(tmpCnt * delta);
}
}
i think this code type will completely what i want to do.
and if rotate encoder, stop, and write something to print angle, it print some error when i rotate faster the error go increase.
i think that's because interrupt tick occurs when interrupt tick was not finished.
so i want to make interrupt routine faster to make doesn't happend like that.
but, i have an idea when you said about cap. when encoder get little impact, the count increase when it really didn'r rotated. and when i check interrupt routine time, it return 28 micro seconds if i calculate it exactly. and it means interrupt routine is possible almost 35,000 hz which is faster than i rotate it. my encoder's maximum speed is 5000 rpm, and it means 7rpm at arduino, but i know i didn't rotate it like that.
but.. i think it doesn't matter about it, because when i test encoder which is didn't broken with myrio, it's currectly measure it.
but i will consider about cap. but what is cap you mean?
and before i apply to my encoder, i want to know why error occurs. i think that's because interrupt routine is too long to check every pulse. so i want to apply pin change interrupt on my code.
regarding the capacitor, read about button bouncing and how to remove that.
Modulo takes some time, you could keep the ISR to the strict minimum which is incrementing or decrementing the counter and do the math when you need to use the counter in the main loop.
void tick()
{
if(digitalRead(phaseA) ^ digitalRead(phaseB))
_count++;
else
_count--;
}
void loop()
{
if(Serial.read() != -1) { // -1 means there was nothing to read
noInterrupts();
while(_count < 0)
_count += ppr;
_count = _count % ppr;
long tmpCnt = _count; // local copy to be safe
interrupts();
Serial.println(tmpCnt * delta);
}
}
thank you for your advise. i will consider that structure.
by the way, while you were offline i was done pin change interrupt.
it's because "PCINT0_vect". i was wrote it first "PCINT_vec" and when i change like "PCINT0_vect", it works. but it still make some error, too.
now i want to make sure that this happen occurs by repeat of interrupt. i guess this happen because rotary pulse that making interrupt pulse is too fast so interrupt occurs before same interrupt finished. but i don't know how to check it.
is there any way to check this? or this problem is another cause? i have no idea about this.
const byte phaseA = 2;
const byte phaseB = 8;
const int ppr = 12000;
const float delta = 360.0/ppr;//12000 is PPR
volatile long _count = 0;
void tick()
{
if(digitalRead(phaseA) ^ digitalRead(phaseB))
_count++;
else
_count--;
}
void setup ()
{
Serial.begin(115200); // *** SET SERIAL CONSOLE TO 115200 BAUDS AS WELL ***
pinMode(phaseA, INPUT_PULLUP);
pinMode(phaseB, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(phaseA), tick, CHANGE);
}
void loop ()
{
if(Serial.read() != -1) { // -1 means there was nothing to read
noInterrupts();
while(_count < 0)
_count += ppr;
_count = _count % ppr;
long tmpCnt = _count; // local copy to be safe
interrupts();
Serial.println(tmpCnt * delta);
}
}
i try to count encoder in two way which one is counting by attachInterrupt
const byte phaseA = 8;
const byte phaseB = 7;
const int ppr = 12000;
const float delta = 360.0/ppr;//12000 is PPR
volatile long _count = 0;
ISR(PCINT0_vect)
{
if(digitalRead(phaseA) ^ digitalRead(phaseB))
_count++;
else
_count--;
}
void setup ()
{
Serial.begin(115200); // *** SET SERIAL CONSOLE TO 115200 BAUDS AS WELL ***
pinMode(phaseA, INPUT_PULLUP);
pinMode(phaseB, INPUT_PULLUP);
uint8_t oldSREG = SREG;
PCICR |= B00000001;
PCMSK0 |= B00000001;
SREG = oldSREG;
}
void loop ()
{
if(Serial.read() != -1) { // -1 means there was nothing to read
noInterrupts();
while(_count < 0)
_count += ppr;
_count = _count % ppr;
long tmpCnt = _count; // local copy to be safe
interrupts();
Serial.println(tmpCnt * delta);
}
}
and pin change interrupt way.
both are most same and able to activate, and has same error occurs.
the error means error.
when i rotate 360 degree the output should be 0
and when i rotate it very slowly the output is 1~359(which means -1)
but when i rotate it little fast like 1 rpm / s
the output is 7~8 or 352~353 like this. or sometimes 120degree like this.
output didn't return 0 or 1 or 359.
so i assume that's because interrupt reiteration. if not i didn't assume else.
you should keep the time with interrupt blocked as short as possible, so not do
noInterrupts();
while(_count < 0)
_count += ppr;
_count = _count % ppr;
long tmpCnt = _count; // local copy to be safe
interrupts();
and prefer
noInterrupts();
long tmpCnt = _count; // local copy to be safe
interrupts();
while(tmpCnt < 0) tmpCnt += ppr;
tmpCnt = tmpCnt % ppr;
Then remember that rotary encoder you buy for ~ a dollar are not top notch quality. You get bouncing, the quality of the contacts might be average, which all starts playing and degrading the ticks as you turn faster.
what model do you have? is it graded for being attached to a motor and be accurate?
4/ the current draw can be 150mA max. How are you powering it ?
————
Why don't you first check if you can get it to work with the library I mentioned above. Once that works, you can dig into it more if you want to (or just move on to your real project)
Can you test this code - open the console at 115200 bauds
Make sure you have a stable power supply able to deliver 150mA. Don't use the 5V pin of your Arduino Uno (the 5V pin may supply less than five volts if you are on USB - or use a Jack power supply at 9V 1A for example). If you do not power directly from your Arduino, join grounds.
#define ENCODER_OPTIMIZE_INTERRUPTS
#include <Encoder.h> // install https://github.com/PaulStoffregen/Encoder
Encoder rotaryEncoder(2, 3); // Green cable on 2, White cable on 3
int32_t oldPosition = 0;
void setup() {
Serial.begin(115200); // open the Serial Console at 115200 bauds
Serial.println(F("Testing Rotary Encoder s66-5-6000VL"));
rotaryEncoder.write(oldPosition);
}
void loop() {
int32_t newPosition = rotaryEncoder.read();
if (newPosition != oldPosition) {
oldPosition = newPosition;
Serial.println(newPosition);
}
}
wow, i'm very impressived. it has some error, but it's very accuate better than my code.
i should applied this as fast as possible when you recommend.
i'm very appriciate with your help, thanks!.
but i have one question. if i analyize that library, then is it possible to know why my code has an error even if i use pin change interrupt, but library is very accurate even if use loop with serial?
sinsin63:
i powered it directly to uno board. i know that board can power 500mA and nothing else connected it, so i think it can supply enough current to it.
USB can provide indeed 500mA but the 5V pin when powered through USB is not exactly 5V. it can be more or less depending on your USB quality and it can vary as you draw current. if your rotary is too sensitive (here seems OK to up to 5% ripple) that can lead to disfunction too.
You could indeed look into the library (prune out the code that does not pertain to AVR) and you'll see how they use interrupts.
How many pulses do you get per tick ? if you get 2 you could modify the code to
#define ENCODER_OPTIMIZE_INTERRUPTS
#include <Encoder.h> // install https://github.com/PaulStoffregen/Encoder
Encoder rotaryEncoder(2, 3); // Green cable on 2, White cable on 3
int32_t oldPosition = 0;
void setup() {
Serial.begin(115200); // open the Serial Console at 115200 bauds
Serial.println(F("Testing Rotary Encoder s66-5-6000VL"));
rotaryEncoder.write(oldPosition);
}
void loop() {
int32_t newPosition = rotaryEncoder.read() >> 1; // divide by 2 as 2 pulses per tick
if (newPosition != oldPosition) {
oldPosition = newPosition;
Serial.println(newPosition);
}
}
if you get 4 pulses then you can do
int32_t newPosition = rotaryEncoder.read() >> 2; // divide by 4 as 4 pulses per tick
if you spin really really fast, then increase the Baud rate and don't print as often (may be every 50 ticks) as that will generate extra interrupts.
Note as well that digitalRead is somewhat slow and thus you are not reading both pins at the exact same time of the interrupt - that would also lead to inaccurate result at fast RPM
Using PORTs to read the status of the pins is way much faster (1 clock cycle to get both pins if they are in the same port plus a bit mask)