2

I'm using a home made "bare bones" Arduino, made with the ATmega328, and I need to read angle data from a geared DC drill motor, turning at speeds between 0 and 450 RPM. This is so I can use it as feedback for an angle control system implemented on the Arduino.

I've not yet bought the encoder. I'm waiting until I find a suitable one which works easily with Arduino.

I have already written the control algorithm. I'm using state variable feedback, and the algorithm depends on a getAngle() function that I have not written yet. This function is inside an if statement that executes every 0.2 seconds. It needs to return the angle of the DC motor in degrees.

My questions:

  • How can I implement this getAngle() function using a rotary encoder?

  • What is an example of a rotary encoder that is suitable for this application?

  • Will it be better to use the interrupt pins of the Atmega328, or use some other method?

I have seen a few library's on Arduino Playground that claim to make reading angle data easier, but how do I use them? I need something that is fast (it, and the control algorithm operations, both needs to finish before the 0.2 seconds sample time), and more importantly it needs to be easy to implement. I'm coming up to the project deadline so I don't want to have to spend a whole day figuring out how to implement this getAngle() function.

Thank you!

  • 3
    This question is cross-posted to Electrical Engineering SEx. Please don't do that. – JRobert May 18 '15 at 14:18
4

You will have to use interrupts or otherwise the encoder might be rotating but your Arduino is doing something else than reading the encoder. By using interrupts, the encoder counter is always updated when the encoder moves. This also means that the interrupt function has to be very short so that it only takes a few cycles to complete and thus leaves the majority of the Arduinos computing time for your sketch.

Here is a code that I've used in many applications from CNC handwheels to servos and for reading extremely accurate angle sensors. It is dead simple, uses two interrupts to read the quadrature signal (A & B channel) and also makes use of Arduinos built in pull-up resistors to keep the channels held high for reliable readings.

Assuming that your encoder is an open-collector output and works with 5V, you only need to connect 5V and GND to it and then the A channel to the first interrupt pin #2 and channel B to #3. That's it for the hardware.

This code goes at the start of your sketch, right before setup() function:

const signed byte encoderDirections[] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};
volatile long encCount = 0;

It defines a constant array that is used for finding the direction that the encoder has turned based on the previous value and the current value. It is defined as constant as it is never changed. Also a global variable calle encCount is defined as a long type variable so that the encoder can count in both directions. It is absolutelu imperative to declare it as volatile so that the compiler and the microprocessor never try to do any "shortcuts" when you use this variable, but instead always loads it from its respective memory address. This is done so because the interrupt can fire at any time and change the variables value without any other function knowing it.

Next, in your setup() function you will want to include these lines to make the Arduino pins as inputs with the internal pull-up resistors connected. The latter two lines attach the interrupts 0 & 1 (hardware pins pins 2 & 3) so that whenever the pin state changes (low to high or vice versa), it halts the microprocessors operation and jumps to the encoder_interrupt function immediately.

pinMode(2, INPUT_PULLUP);
pinMode(3, INPUT_PULLUP);
attachInterrupt(0, encoder_interrupt, CHANGE);
attachInterrupt(1, encoder_interrupt, CHANGE);

Last you will need the actual interrupt function that reads the encoder and updates the counter value.

void encoder_interrupt() {
  static unsigned int oldEncoderState = 0;
  oldEncoderState <<= 2;
  oldEncoderState |= ((PIND >> 2) & 0x03);
  encCount += encoderDirections[(oldEncoderState & 0x0F)];
}

First this defines a variable that holds the previous encoder value that was read from the pins 2 & 3. This is declared as static, so the value is stored in memory even when this function exits so that it can be used the next time the interrupt routine is run.

The second line moves the previous interrupt channel values two bits to the left (multiply by 4) to make room for the new values.

The third line is an interesting one. It reads the PIND, meaning all the pins at D register, which holds many Arduino pin states, including the pins 2 & 3 that we are interested in. Next it shifts this value 2 bits to the right (divide by 4) to "drop" the pin 2 & 3 states to the lowest bits of this byte. Then it AND's this byte with 0x03 to zero everything else but the lowest two bits that now contain the interrupt channel states. Lastly these two bytes are OR'd with the previous encoder state values to obtain a 4 bit number.

On the forth line this 4 bit number is AND'd with a 0x0F to make sure the highest 4 bits are zero to make sure this number is truly a 4 bit number that stays between 0..15, as it is used as an index for the constant array we defined at the start of the program to get the encoders direction of rotation and this is then added to the counter variable.

And now you have an accurate and extremely fast way of reading a two channel encoder to keep track of its position.

If you want to calculate an angle from the encoders current position, you could do a function like this, but you have to know the line count of your encoder and multiply it by 4 so you know the encoder pulse count per revolution. For example, a 100 line rotary encoder gives out 400 pulses per revolution. I assume that there is a constant defined as follows at the start of program:

const int LINE_COUNT = 400;

Also, this function assumes that the encoder could have turned more than one revolution and you just want the angle from some reference spot in degrees. Do take in to account that the long type variable will overflow after +-2 147 483 648 pulses as it is a 32 bit signed variable.

float getAngle() {
  long curCount = encCount;  // Store the current count.
  while(curCount < 0) {
    curCount += LINE_COUNT;
  }
  while(curCount >= LINE_COUNT) {
    curCount -= LINE_COUNT;
  }
  return (curCount / LINE_COUNT * 360.0);
}

First the encoder count is copied to a one use variable. then two while statements make sure the values is positive from 0 to LINE_COUNT-1, so it basically reduces the count to what it would be with just under one revolution. Then it returns the number of degrees by calculating the current position as a fraction of a one revolution count and multiplies this with 360 to get the degrees.

  • This is exactly what I was looking for. Thank you. – Blue7 May 20 '15 at 13:42
1

Your encoder choice will probably be driven by the mechanical requirements and the precision you need:

  • Runs occasionally, and briefly to 450 RPM or runs continuously, and often at the higher speeds?
  • Do you need position to e.g., within 3 degrees (7 bits)? Or 1/3 degrees (10 bits)? Or are you willing to count pulses from an index mark (this gets more complicated if the direction of rotation can change).

An n-bit encoder can probably be read whenever you need a position sample, so interrupts would probably not be necessary. With an index wheel (a pulse every x degrees, plus a separate pulse at 0 degrees, e.g.) you would probably benefit from tracking the position at interrupt level and letting the main loop simply access the last known position.

Your getAngle() implementation depends totally on the choices you make above. If you use a Gray-coded encoder wheel, you'd need to convert the code to a binary fraction and multiply by 360 to get degrees. If you use an index wheel, getAngle() just reads the current pulse-count maintained by your interrupt routine and converts it to degrees. But you need an interrupt routine to count angle-pulses and reset the count on the index pulse (not especially hard to do).

Fixed-point arithmetic is pretty fast. Floating point takes longer. Depending on what else, besides position-keeping, your code needs to do, and how math-heavy it is, whether you can afford floating point. The floating point library will take more code space, too, but my guess is you'll be constrained by time more than by code-space.

1

There is no such thing as an encoder that "works well with Arduino" - encoders are encoders, and there are several basic types. As @JRobert said, you need to make an appropriate choice, and an encoder that will mechanically couple well with your drill motor is probably your primary criteria, and of course an encoder that can spin unrestricted (some are only for an angular range like 180 or 270 degrees); my guess is you'll want to put the encoder on the ungeared motor, rather than the geared output of whatever it is you're driving, to get good feedback from a cheap (i.e. relatively small number of 'pulses per rev') encoder. Search on DigiKey, Mouser, online hobby stores, etc. Then just deal with whatever type of encoder it is, hack its electrical interface into the Arduino: http://www.digikey.com.au/product-search/en?pv69=848&FV=fff4001e%2Cfff80033&k=encoder&mnonly=0&newproducts=0&ColumnSort=0&page=1&stock=1&quantity=0&ptm=0&fid=0&pageSize=25

This (2-signal quadrature) encoder library for Arduino is superb, by Paul Stoffregen @ pjrc.com: https://www.pjrc.com/teensy/td_libs_Encoder.html For best results you'll want the 2 'quadrature encoder' outputs to go into 2 interrupt inputs on the Arduino, and there are only 2 available. There is an option to run this library on only 1 IRQ-capable input (the other can go into any input), or even no IRQ-capable pins, but performance will suffer.

Sampling at only 5/second isn't much - don't be afraid to increase that substantially - 10 to 50 / second.

You also might want a PID control algorithm, depending on what your actual aims are. the "PIDLibrary" by Breatt Beauregard on the Arduino Playground website is excellent.

Coding your 'getAngle()' function shouldn't be hard - for a rotary quadrature encoder, X pulses from the encoder corresponds to 1 revolution. The 'encoder library' you end up using may provider more than 1 pulse output per actual pulse output from the encoder. Do that math. Simple.

1

You are mixing two related ideas together. Keeping track of encoders is best done with at least one interrupt in my experience. Whether or not you should use 2 or can get away with none depends on the speed of the shaft. In my experience, RPMs generally encountered at the hobby level can be handled by 1. So your interrupt code is interrupted by phase A rising or falling (Pin Change Interrupt). At that point it read phase B, depending on the combination of A and B you are either going forwards (interrupt count+) or backwards (interrupt count-). You know by specifications how many pulses you will have in a 360 degree rotation, and by modulo math, (e.g. x % 360) you will know how many degrees you are currently at.

The concept of sampling rate has to do with how often you read the current interrupt values, but they themselves will always be accurate and realtime.

Keywords to google volatile. Also use signed long integers, not unsigned (don't ask how I know :| )

Not sure why the other answer suggested a PID, I didn't see anything in your question that would need it. For holding an angle you are more likely to use acceleration and deceleration, not PID.

Search google, you might find a way to build your own encoders.

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.