Velocity from encoder with Timer Interrupts - Page 1
Forum section: electronics / general

author: oneevening
author_rank: Newbie
timestamp: June 19, 2015, 12:04:46 pm
content: Okay, for days I am struggling to calculate the velocity out of my quadrature encoder with arduino. My problem is the following;Since I read the encoder with an ISR function, which is triggered by the A and B channels of the quadrature encoder, I cannot measure the time elapsed.So, I thought that for velocity calculation, normal ISR is not appropriate. What I need to do is using a timer Interrupt.However, my question is that; Can I use an ISR attached to a pin and also a timer interrupt at the same time. Would that lead a problem. For example my normal ISR is in progress to read the encoder increment and at that instead timer interrupt needs to be started. Would it terminate the normal ISR, or it would wait and right after normal ISR finished, timer interrupt would start ? How does it work ?So my plan is to read the encoder with a normal ISR, this will change a variable called "encoder" and then there will be another parameter, which is only edited by timer interrupt, let's call it "timer" and it is initialized as "0". And assume that the timer interrupt works at 1Khz. So, I will do the following operations in the timer interrupt;QuoteWhich should simply calculate the rate of change of encoder ticks.Any better ideas ?

author: mikerj
author_rank: Super Contributor
timestamp: June 19, 2015, 12:28:19 pm
content: Triggering interrupts from a quadrature encoder output is often not the best way to do things to start with, since contact bounce can trigger many more interrupt than you may be expecting.However, if you want to go this way then you could set up a timer and use the encoder output to trigger a timer capture event. In the capture interrupt you can subtract the previous time from the current time to get the time between interrupts.Provided you use unsigned math this work correctly even if the timer overflow occurs between two encoder edges, though the maximum possible you can measure is half the timers total period. This means you will need some scheme to determine if the last time value is invalid.

author: langwadt
author_rank: Super Contributor
timestamp: June 19, 2015, 12:52:51 pm
content: Quote from: mikerj on June 19, 2015, 12:28:19 pmyou don't need the capture interrupt you'd only need to set the timer for up capture, the captured valuecan be read in the pin interrupt

author: oneevening
author_rank: Newbie
timestamp: June 19, 2015, 12:54:03 pm
content: Thanks for your reply.I am not necessarily stick to this method. If you suggest a better solution it is more than welcomeQuote from: mikerj on June 19, 2015, 12:28:19 pm

author: nctnico
author_rank: Super Contributor
timestamp: June 19, 2015, 01:28:51 pm
content: Others have posted code to deal with encoders a long time ago. Besides that: never use interrupts from signals unless you are 1000% certain about their maximum rate. In case of an encoder this means either to use polling or proper filtering in the hardware.

author: oneevening
author_rank: Newbie
timestamp: June 19, 2015, 01:52:05 pm
content: What is this polling ? Can you elaborate on ?Quote from: nctnico on June 19, 2015, 01:28:51 pm

author: nctnico
author_rank: Super Contributor
timestamp: June 19, 2015, 02:50:42 pm
content: Quote from: wilfred on June 19, 2015, 02:18:08 pmOne of the secret requirements of interrupts is that you have to be able to predict them. External signals without filtering can (and will!) create situations in which you'll get a high number of interrupts which can cause erratic behaviour or even to make the system come to a grinding halt. It is an error often made by novices and software engineers who think a piece of hardware will never fail and is always installed in a laboratory environment.

author: mikerj
author_rank: Super Contributor
timestamp: June 19, 2015, 03:19:09 pm
content: Quote from: langwadt on June 19, 2015, 12:52:51 pmThe point is that you wouldn't need two interrupts in either case, you could either handle everything in external interrupt handler handler or in the timer capture interrupt handler.Quote from: oneevening on June 19, 2015, 01:52:05 pmPolling simply means regularly reading the value of something to look for changes. You could use a timer interrupt to do this for your encoder. This means your processor has some overhead all the time, even when the encoder isn't moving. However, it also means that this overhead is pretty constant, even for fast encoder movements, or with lots of contact bounce etc.

author: Jeroen3
author_rank: Super Contributor
timestamp: June 19, 2015, 05:37:53 pm
content: We actually discussed this earlier.https://www.eevblog.com/forum/microcontrollers/esp8266-pushbuttons-debounce-how-to-easy-debounce-story/The only exception I would make for interrupts on user input if the source is an heavily hardware low-pass filtered pushbutton on a ultra low power application.Interrupts can ruin your day when the knob wears out and starts bouncing all over the place. It also limits the maximum knob speed to the cpu usage, instead of a predetermined polling speed.You won't notice high cpu usage, but you can easily detect missed polling cycles.

author: oneevening
author_rank: Newbie
timestamp: June 19, 2015, 09:40:01 pm
content: Quote from: mikerj on June 19, 2015, 03:19:09 pmI am not quite sure how can I handle everything with one timer interrupt or input interrupt. Input interrupts, in theory, watching for every pulse from A and B pins of the encoder and make the positive or negative increment. However, here I cannot record a reference sampling period. However, if I only use a timer interrupt, then I cannot catch every encoder pulses.Where am I wrong ?Quote from: mikerj on June 19, 2015, 03:19:09 pmHere I need to look for changes in a fixed time period. However, I also need to handle the "changes", which has to be handled by another ISR, which is triggered by the encoder pin signal inputs. Otherwise how can I store the changes ?

author: codeboy2k
author_rank: Super Contributor
timestamp: June 19, 2015, 10:31:16 pm
content: If you have more than 1 encoder, then look for a software solution. If you only have 1, I'd say do it in hardware and save time trying to write and debug the software.This EDN design idea provides a direction and count output from a rotary encoder, and increases the count rate when you turn it faster.http://www.edn.com/design/systems-design/4416528/Adaptive-rotary-encoder-distinguishes-fine-from-coarse

author: oneevening
author_rank: Newbie
timestamp: June 19, 2015, 10:35:22 pm
content: Quote from: codeboy2k on June 19, 2015, 10:31:16 pmUsing hardware would definitely be easier but I am also trying to keep the costs low.

author: TerminalJack505
author_rank: Super Contributor
timestamp: June 20, 2015, 03:39:15 am
content: Here's some code I wrote while I was doing some informal research on rotary encoder velocity. The code's still half-baked but it might give you some ideas. I used an Atmel ATtiny2313--a MCU similar to the one on the Arduino board--for the research.The strategy I used to determine the velocity was to count the number of "encoder actions" during a fixed sample period. I then multiplied this by the sample frequency to determine the number of actions per second. Negative values represented counter-clockwise velocity. Positive values represented clockwise velocity.I used "four phase per click" rotary encoders, so an "encoder action" is the progression of the two channels from "11" -> "01" -> "00" -> "10" -> "11" (clockwise action) or from "11" -> "10" -> "00" -> "01" -> "11" (counter-clockwise action.) (See the attached FSM diagram.)The sample period is controlled by a 16-bit variable called 'SampleTick'. (An 8-bit variable would work as well.) This variable is incremented by a timer interrupt at a fixed rate. I played around with different update rates and found rates of 2 Hz to 10 Hz to be ideal.When the encoder's Update() method runs (the method can be called via polling or via pin change interrupt) it uses SampleTick to determine if it is in the same sample period that it was in when last called or if it is in a new sample period. If SampleTick is the same as it was when last called then it knows that it is in the same sample period and any new actions are added to a working velocity. (It's a little more complicated then that since you have to watch out for direction change during a sample period.)If the Update() method is called and SampleTick has changed from the previous call then a new sample period is started. In this case, _if_ the two periods are consecutive then the code can use the accumulated encoder actions (aka working velocity) to determine the current velocity.

author: mikerj
author_rank: Super Contributor
timestamp: June 20, 2015, 10:02:45 am
content: Quote from: oneevening on June 19, 2015, 09:40:01 pmYou could trigger a capture interrupt from one of the encoder pins. Within the interrupt handler you read the state of the other pin, presumably exactly what you are doing now. However, you also now have a timer value captured, so you can compare the current time to the previous time.As I and others have said though, using interrupts triggered directly from an encoder with no filitering can be risky. You could limit the interrupt rate within your code, but that will require an extra timer anyway.Quote from: oneevening on June 19, 2015, 09:40:01 pmYou don't need any ISRs at all with polling since you could do it within your main loop, but it's usually more convenient to set up a timer interrupt to do this. As for "storing changes", it makes no difference whether you are polling or running off an edge triggered interrupt. Either you are storing encoder counts in a variable somewhere and polling this value from your main loop, or you are calling a function from your encoder interrupt if the encoder value has changed. Either method remains exactly the same for a polled solution.

author: oneevening
author_rank: Newbie
timestamp: June 21, 2015, 05:08:58 pm
content: Quote from: mikerj on June 20, 2015, 10:02:45 amYes, I attached both A and B pins of encoder to same ISR to trigger it. Then I did the necessary bitwise operations to decide either add 1 or subtract 1 from the enc_count variable.I don't have the encoder setup with me for the time being.So, my code as follows. I simply take the time at the beginning of the ISR and use this time to compare with actual time to find the time elapsed.Displacement is always 1 because I am always looking the time elapsed between each encoder ticks.http://pastebin.com/ZetNMbPiAm I on the right track ?

author: codeboy2k
author_rank: Super Contributor
timestamp: June 21, 2015, 11:34:54 pm
content: I'm jumping in here..I took a quick look. That code looks reasonable so far. I don't know about the call to millis() in your ISR.Code:last_time = millis();Does this work inside the ISR ? Just a warning to make make sure that it won't be a problem area for you.Second, you would be better to get the table lookup first and do something only if it's non-zero. That way you don't call millis() when you don't need to, since it might be a slow operation:(here's your own code with some of my suggested changes)Code:void encoder(){int8_t direction;long this_time, diff;enc_val = enc_val << 2;enc_val = enc_val | ((PIND & 0b1100) >> 2);direction = lookup_table[enc_val & 0b1111];if (direction) {this_time = millis(); // millis() only lasts 50 days and may overflow.diff = this_time - last_time; // you will need to deal with overflow here// TODO: set multiplier equal to 1, 2, or 5, for example, depending on time elapsed.enc_count = enc_count + direction * multiplier; // this may go negative , is that OK?last_time = this_time;}}Thirdly, you really should low pass filter your A and B encoder inputs with at least an RC time constant of of around 20-30 ms. Bounces can cause 100's of singular microsecond changes that can last a total 10-50 ms long. Yes, a fast processor can probably handle the 100k per second (or more!) interrupt rate, but why do that? You really don't want that when most of them will be thrown away, and it will become troublesome when you want to do other things but are hindered by a high interrupt rate caused by switch bounce. This will manifest itself as issues in other areas of your code when you try to rotate the encoder.A simple RC debouncer on the switches is all you need.See below. The horizontal line on the simulation marks the approx. 2.0V CMOS threshold. Some circuits use a schmitt trigger buffer before going into the microcontroller input pin, but if you can set the micro to use level-triggered interrupts, not edge triggered, then you can just go straight in with this circuit. Many micro's have schmitt trigger inputs anyways.(note: the diodes are not absolutely necessary -- they help the rise time, and keep the rise time and fall time about the same ... in this example it is about 20-30ms)

author: nctnico
author_rank: Super Contributor
timestamp: June 21, 2015, 11:54:29 pm
content: Quote from: codeboy2k on June 21, 2015, 11:34:54 pmONLY IFthe inputs of the microcontroller are of the schmitt-trigger kind which they usually aren't so you'd need to add these in the form of (for example) a 74HC14.

author: codeboy2k
author_rank: Super Contributor
timestamp: June 22, 2015, 12:11:20 am
content: Quote from: nctnico on June 21, 2015, 11:54:29 pmYep. I mentioned that toosome micros do, some don't have schmitt-trigger inputs. Bonus if they do.

author: nctnico
author_rank: Super Contributor
timestamp: June 22, 2015, 12:45:56 am
content: Quote from: codeboy2k on June 22, 2015, 12:11:20 amSchmitt-trigger is a hard requirement! Some microcontrollers have inputs which switch around 0.7V with no hysteresis at all so even a small pulse will flip the input.

author: codeboy2k
author_rank: Super Contributor
timestamp: June 22, 2015, 12:59:00 am
content: Quote from: nctnico on June 22, 2015, 12:45:56 amI agree schmitt trigger is a hard requirement, but many micros have this internally. If not, then you need it externally. I mentioned this, perhaps not so clearly.Plus, which micros will register a logic high at 0.7V input? that's a clear violation of 3.3V CMOS standards where Vt=1.5V and VIH=2.0V. Perhaps at 2V CMOS or 1.8V CMOS, sure, but that's a different design thenand I agree there.

author: Jeroen3
author_rank: Super Contributor
timestamp: June 22, 2015, 05:43:11 am
content: I've yet to encounter a micro without schmitt triggering. They all seem to have schmitt triggers, as well as ESD HBM diodes.The voltage level of an IO is not 0.7 by default, its a formula based on the voltage and if the chip is 5V tolerant.Standard pin minimum high level: 0.41*(VDD-2V)+1.3 V. At 3.3V = 1.833.Standard pin maximum low level: 0.28*(VDD-2V)+0.8 V. At 3.3V = 1.164.With 200 mV schmitt trigger hysteresis.5V tolerant pins are slightly different.Read the manual: STM32F103xF

author: oneevening
author_rank: Newbie
timestamp: June 23, 2015, 10:28:35 am
content: Thank you for the thorough answer.I realised that I do not need to count the ticks, since I am only interested in the elapsed time between each quadrature. So, that would speed up my code a little more. (I am only interested in velocity in one direction)I tried input interrupt without low pass filter and it was looking fine until I go full speed, which is 2600 RPM with 256 per count encoder resolution.At that full speed encoder was still reading but there was some value jumps. Delta t (elapsed time between two interrupt calls) was around 12 microsecond and there are some values way greater than it, such as 64520 and 16711688 microseconds.Tried the low pass filter suggestion.Calculated the Fc (cut of frequency) as 6.36 Hz by selecting the time constant 25 millisecond = 0.025 second.Found the R and C values as 240 Ohm and 100uF respectively. Used 220 Ohm instead though.Now at full speed I don't have those jumps on the delta t value. However, at low speeds I still get delta t of 8 and values around that. Which is totally wrong.Also I tried the count the number of ticks and realised that with low pass filter, encoder does not read any ticks.So, what might be the problem ?Quote from: codeboy2k on June 21, 2015, 11:34:54 pm

author: Bassman59
author_rank: Super Contributor
timestamp: June 25, 2015, 06:05:49 pm
content: Quote from: oneevening on June 23, 2015, 10:28:35 amI would start by using an oscilloscope to look at the encoder signals at the micro pins.NB: the 'scope reveals much. I designed something around a Bourns PEC09 encoder with detents, and couldn't understand why the micro was double-incrementing with each turn. It turns out that these particular guys (PEC09-2320F-S0015) has 30 detents but only 15 pulses per rotation, and as you turned it to each detent, first one output would change and then soon the other one would change. It was weird. I ordered some without detents and also the version that has 15 detents instead of 30, and though I haven't tested them yet I think they should step through quadrature as I expect.

author: codeboy2k
author_rank: Super Contributor
timestamp: June 26, 2015, 04:10:46 am
content: Quote from: Bassman59 on June 25, 2015, 06:05:49 pmThat's all I can say too. Let's see some scope traces at high and low speeds. I also would not have chosen such a high-value capacitor, as you did. I'm not yet saying that's the problem, but my schematic shows a 0.1uF cap and a few hundred K for the resistors. When you use a higher cap and lower R it presents the RC circuit with a much lower impedance and draws more charging current from the voltage source, and this might be a problem for you. That's why I chose a higher resistance with lower capacitor for the RC circuit -- it's a much higher impedance load for your supply and charges with much less current requirements from the voltage source. It may or may not be a problem for you here, but I'm just pointing that out.Quote from: oneevening on June 23, 2015, 10:28:35 amI don't know what you mean here. With a low pass filter (and schmitt trigger inputs on your micro) you still get one interrupt for each debounced input. You can still count these.