Encoder & SerialPrint() issue

Hello everyone who reads this. I've got a fairly precise industrial encoder with high resolution. After a little dinking around I got the Arduino to read it wonderfully, now I'm trying to make use of it but have hit a brick wall. I need to send out the encoder position (possibly to another Arduino, maybe my computer or a display), and the only way I can figure is with SerialPrint() - however when I invoke SerialPrint() it seems all other functions stop during this period (which is rather lengthly in the microprocessor world) - which means it's not checking for pulses in that time and thus loosing many positions. Attached is code that works okay, but it spits out position every second, rather than every pulse (like I tried at first). Right now I'm thinking maybe try to control a digital potentometer and have my secondary microcontroller read the digital pot? I dunno. If anyone has any ideas on how to send a number out without using SerialPrint(), I'd greatly appreciate them. Thanks!

/*Designed for AutomationDirect's TRD-S/SH1000 VD Encoder (1000 pulses per revolution)
0V (blue wire) goes to GND
5V (brown wire) goes to 5V
A+ (black wire) goes to Arduino Digital 2
B+ (white wire) goes to Arduino Digital 4
everything in loop() is just for verification.
This setup should be accurate to roughly 0.3 degrees.
*/
volatile double c = 0;
volatile double pos = 0;
#define A 2
#define B 4
void setup(){
pinMode(A, INPUT);
digitalWrite(A,HIGH); // internal pull up resistor
pinMode(B, INPUT);
digitalWrite(B,HIGH); // internal pull up resistor
attachInterrupt(0,position,CHANGE);
Serial.begin(9600);
}
void loop(){
delay(1000);
Serial.println(pos);
}
void position(){
if (digitalRead(A)==LOW && digitalRead(B)==HIGH){
c=c+1;
}
else if (digitalRead(A)==LOW && digitalRead(B)==LOW){
c=c-1;
}
else if (digitalRead(A)==HIGH && digitalRead(B)==HIGH){
c=c-1;
}
else {
c=c+1;
}
pos=((c/2000)*360); // divide count, multiply by 360 to get degrees.
}

pos=((c/2000)*360); // divide count, multiply by 360 to get degrees.

While probably not related to your problem directly, the above code is better placed in the loop function instead of inside the ISR code. The rule for ISR functions is to be as small and quick as possible.

Lefty

I'm playing around with controls. Ultimately I'm wanting to control the position of a stepper motor through the use of an encoder (I know that's what servos are for, but have you seen the price on those motors? let alone the control boards!). I'm not sure what speeds yet, if I had to guess, maybe somewhere in the 200-500 rpm range? I'm supposing that I might be able to tell the Arduino what position to go and allow it to look at the encoder, and control the motor at the same time. But this still means I need a serial connection with another microcontroller or computer. The more I play with it, the more I think the Arduino simply isn't powerful enough for this task.

One of the nicest things about stepper motors is that you can know where the shaft is by how many steps you sent

...assuming you knew where it was to start with.

assuming you knew where it was to start with.

Same issue with a shaft encoder, so that is a wash.

A zeroing routine (home switch) ensures you know where you are as long as you have power.

Precision control, such as CNC machining.

I'm simply asking for help, not a berating.

Same issue with a shaft encoder, so that is a wash.

Ah! You're talking about an incremental encoder.

I don't understand why Serial.println would slow down your ability to recognize encoder pulses. Since the encoder changes trigger interrupts it seems to me that only excessive shaft speed would overrun the interrupts. Nothing that happens in your loop() function should affect this.

Now, having said this you could make your position() function a lot faster and thus handle higher shaft speeds. Every call to digitalRead() wastes time. It would be better, as a first cut, to just call digitalRead() once for each encoder pin, save the result in a variable, and check the variables rather than call digitalRead() each time you have a check.

Even faster is to bypass digitalRead() and read the port pins directly using the PINA, PINB, etc. registers.

Division is also a fairly expensive operation and the expression:

pos=((c/2000)*360);

has problems. First, 'c' is a double which means it takes a long time to do arithmetic with it. Better is to declare 'c' as 'unsigned'. Then, instead of doing an expensive division/multiplication operation by a factor of 360/2000==0.18 you can multiply by 23/128==0.1797, which is pretty close. And that's easier because division by 128 is a shift right by 7 bits. If you want resolution to a tenth of a degree you can multiply by 230/128 and interpret the integer result as "decidegrees" (i.e., 123 means 12.3 degrees):

unsigned decidegrees;

decidegrees = ((c*230UL) >> 7);

Finally, there is some benefit to having an encoder on a stepper. It can tell you when you are pushing the stepper too hard and are losing steps. Conversely, you can have encoder-driven motion in which every X encoder counts (X=2000/200=10 in your case) you can step forward rather than stepping on a prescribed time schedule. This ensures that you are accelerating at the fastest possible speed for a given point on the torque-speed curve without losing steps.

-- Check out our new shield: http://www.ruggedcircuits.com/html/gadget_shield.html

My first draft of this program didn't even calculate the position. I just had it as bare as I could think to make it, printing out the count 'c' with each pulse, and that seemed to limit me to 2 or 3 rpm before I'd start noticing missed pulses. After tinkering for a while (superficial and basic stuff, nothing like changing clock speed or anything), it seemed that the Serial.Println was the only change that affected the speed, which is why i've got it printing out every second rather than with each pulse. When you say:

check the variables rather than call digitalRead() each time you have a check.

does that mean something like:

if(newA==oldA)
      //stuff to count up
  else
      //stuff to count down

(which is kind of what I did, so I'm guessing no) is there some other type of check I'm unfamiliar with?

Thanks to everyone for their input, I appreciate all your time.

Your initial post contained this:

void position(){
if (digitalRead(A)==LOW && digitalRead(B)==HIGH){
c=c+1;
}
else if (digitalRead(A)==LOW && digitalRead(B)==LOW){
c=c-1;
}
else if (digitalRead(A)==HIGH && digitalRead(B)==HIGH){
c=c-1;
}
else {
c=c+1;
}
pos=((c/2000)*360); // divide count, multiply by 360 to get degrees.
}

Horrid indenting aside, there are 6 calls to the digitalRead function. Depending on what is read by the 1st two calls, the same pins may be read once or twice more.

void position()
{
   byte aVal = digitalRead(A);
   byte bVal = digitalRead(B);

   if(aVal == LOW && bVal == HIGH)
     c++;
   else if(aVal == LOW && bVal == LOW)
     c--;
   else if(aVal == HIGH && bVal == HIGH)
     c--;
   else
     c++;

   pos = ((c/2000)*360); // divide count, multiply by 360 to get degrees.
}

This code accomplishes the same thing, with a maximum of 2 digital reads.

Looking at your code, I would not say that Serial.Print is the issue. You're using delay(1000) in the loop. This basically tells the Arduino to go to sleep for a second, and nothing happens during this time. In the Arduino IDE, open the sketch called "Blink Without Delay" and check it out.

If you want to trim the fat in the Interrupt Service Routine even more, you could replace the digitalReads with direct port manipulation. You could also eliminate all of the checking to see which output is high or low - the outputs will always be equal (both low or both high) when the encoder turns one way, and always unequal when it turns the other.

void encoderISR {
     if (bitRead(PORTD, 2) == bitRead(PORTD, 3) ) {
          encoder += 1;
     }
     else encoder -= 1;
}

This is about as quick and dirty as it gets. This will read a quadrature encoder connected to digital pins 2 and 3 and increment a global variable "encoder." Declare encoder as a long to make sure you don't fill it up, and you'll be okay. The interrupt needs to act on "change" to make sure that it looks at both rising and falling pulses on whichever pin you choose as the interrupt trigger.

Does your encoder have an index output? I frequently use encoders mounted in dry gas meters for measuring gas volume and these generally don't have index outputs. I also use incremental inclinometers to measure angle with respect to gravity, and these do have index outputs.

You can easily output a single character when the output is incrementing and another when it's decrementing. I don't know if the serial output pre-empts the interrupts but I'd guess that it doesn't. I keep a running total in a variable and just output that at intervals. With inclinometers, the total isn't important, the output is always between -360 and +360 degrees.

In a gas meter, I get 720 pulses per revolution, and the meter turns 3.8 times per liter. At 30 liters / minute (approx. 1 cfm,) that's about 80,000 pulses per minute or 1368 Hz. The Arduino can cope with this just fine. Your encoder might be sending faster than mine but I think you'll be okay.

I don't know what you are trying to control with this setup, or how you plan to write the rest of the code. If you're just trying to get the motor to move to a certain spot, I think you can do it but you'll have to be careful about the acceleration and feedback, etc.

Have you looked at the Servo Shield? Might be worth a look.

Good luck!

You know, I was highly confused after I thought about it, but delay(1000) didn’t seem to bother the interrupt routine. It was actually working better with it in the program. It seemed that the delay was only delaying everything in loop() (basically just serial.print) while not affecting the attachInterrupt() or counting. I’ll have to dig deeper to see if this actually is the case or not.
I’m not sure if it has an Index output; it’s a line driver style, with a Z in addition to A and B (as well as A-, B- and Z-, which I’m not using). The Z is a 1 pulse per rev ‘homing’ signal (maybe this is what you’re referring to? I’m not an electronics guy, so I’m unfamiliar with a lot of the terms).
I’m not quite sure how in depth I’m going to be controlling a stepper with this configuration, I’m kind of exploring right now. Ideally I’d like to use the Arduino to take direction from a PC based CNC control. But I’m really testing it’s (and my own) limitations, to see if it can be done.
I may have to settle on manually inputting a position from a keypad or something (with perhaps a default speed, acc & dec) and have it move on it’s own.