Hello Ralph, I am glad you found time to test my code and that you liked it. I also enjoyed your video. I just want to explain why I did not use interrupts. First, I am a Raspberry Pi guy and I am not very skillful in interrupts - I just translated the code for Arduino so that those who are more familiar with Arduinos can make an easier transfer. And secondly, I just wanted to show that the logic works and that the state of the encoder can be tracked by only one variable (that's why I use that bizarre number 14) - I did not spend much time optimizing the code further.
The use (or lack) of interrupts was most certainly not a deal breaker here, Marko. I just loved your logic of having "valid" states, even through it took me weeks to get through your explanation (I was house-moving at the time, that's my excuse). Thanks for the initial comment that triggered me into delving deeper; many of my viewers learned something from our combined efforts! Happy New Year 2022!
I went with a somewhat different technique that debounces by sampling at a fixed frequency (using a hardware timer interrupt), and looking for a pattern of N high samples followed by one low sample (to detect a falling edge after a stable high). When turning an encoder by hand, the states change slowly enough (not counting bounces!) that sampling works fine. To simplify things, I noticed that only one of the pins bounces at a given time. Also, I wanted my program's value to change once per physical detent on the encoder, which corresponds to the rate that one of the inputs is changing. So, look for a falling edge on the "clock" input, and simply sample the "data" (or "direction") input to determine direction (it will be stable at this point, and does not need additional debouncing). Since I'm turning the encoder by hand, I found that sampling the "clock" pin at 10 kHz (every 100 microseconds) worked well. It's fast enough to keep up with the fastest I can turn the knob, and slow enough to detect and ignore bouncing. I use a single byte (uint8_t) to maintain the state of the last 8 samples of the "clock" pin. It triggers on 7 high samples followed by one low sample. Here is a snippet of the important parts: volatile uint8_t encoder_clk = 0xFF; void debounce_callback(void) { encoder_clk = (encoder_clk
Sounds good, Mark. Rolling your own means that you not only understand your own solution but also the problem you were trying to fix! I wanted to put a couple of Schmitt triggers on a rotary encoder (I don't _think_ I did this in a previous video) but never actually got round to designing the simple circuit and board for it. It was in my pre-PCB days.
Ralph, please ignore this if you've seen another comment from me, somehow I don't see it here. Aneng is a surprisingly good brand. I bought one AN8008 couple of years ago, just to carry in my backpack. Did not expect much from it at first but it exceed my every expectation, so much that I bought another one after a couple of months. I've dropped them, did every kind of mistake one can do with a DMM and both are still working flawlessly. They both agree with my trusty Fluke 87V in all ranges, I can strongly recommend it. Keep in mind that this is for the AN8008 model, I don't know about the other models.
I got notified of your previous comment (where has it gone?) so I looked at the AN8008 on your recommendation. In the UK they are £43 (about $58) from Amazon, beyond my budget. However, if ordered directly from Banggood or AliExpress they can be had for $20 _including VAT (sales tax)_ which is only £14.75 - an absolute bargain! I've added this one to my short list! Thanks for the heads up (and re-commenting, a number of my viewers are complaining that their comments disappear. That's even _after_ YT have notified me of those comments. I think some comment-bot has gone mad).
Great video. I modified your code for the STM32 Bluepill and it works great - best rotary encoder code I have ever used. Changes were minor: commented out the #include , defined pins as PB6,PB7 and PB8, removed the IRAM_ATTR from the interrupt routine, and removed the digitalPinToInterrupt() from the attach Interrupt routines. Worked perfectly on the first load. Thanks for sharing this.
Great to hear, Edward! Although I would have kept the digitalPinToInterrupt as it abstracts the physical pin away from the interrupt pin number (assuming you're using Arduino-speak). But, it worked, nice!
@@RalphBacon In the STM32, any pin can be an interrupt, so you just use the pin numbers. My interrupts look like this: attachInterrupt(PIN_A, rotary, CHANGE);
@@RalphBacon Yes, - #define PIN_A PB6 - and when I said that all pins can be interrupts, that's true, but not all at the same time. If you use PB6 as an interrupt, you can't use PA6 or PC6 - there are only 16 interrupts - one for each bit position in the GPIO registers.
This also works with polling instead of using interrupts FYI. I'm doing a project with ±16 encoders and some switches on an ESP32 so I don't have enough interrupt pins. I'm using multiple HC165 shift registers to read my input (encoders+switches) states, and I'm polling for changes. The function from Marko Pinteric works like a charm! I've struggled with getting correct values from my encoders for many hours, but this seems to work perfectly :)
Thank you for bring Marko Pinteric's excellent article to my attention -- had to walk the code several times until the "aha" moment. Now I get it - and why you pass over the "black box" code in your review -- takes a bit to get the workings of lrmem and lrsum
Back in the days of yore, sorry, 1985 they probably built these things to _last_ - unlike today where built in obsolescence and/or servicing is more a important cash cow. What, me, cynical?
I think this proves the fluke the better value when you amortize the cost over the years of ownership. The last time I purchased any. Was a few years ago and they are still in operation. Pro or hobby they are the better value
In this case you are dealing with a sliding contact instead of a bouncing contact. The lowest resistance path is continually changing in location as well as in resistance value. This noise persists until the slide leaves the metal and the resistors pull up the pin voltages to Vcc. Also, quadrature counters simply count back one when the switch 'bounces' and then up again when the switch re-closes -- there is an exception if both switches are bouncing... which is why cheap encoders perform poorly.
Noise is not so much of an issue; it's the state change that will issue the bounce (or actual change, of course). There are so many ways to do this, I might try a hardware solution next.
It's also not a bad idea to put a small capacitor across the A and B switches. Not only does it serve to denounce a little, the current from charging and discharging the cap helps keep the contacts deoxidized.
@@davidwillmore Low-pass filter followed by Schmidt trigger helps too. The Arduino inputs might have a little hysteresis, but another stage don't hurt performance.
Some years ago, when I retired my company issued Fluke had to be returned. I had the same dilemma as yourself. Rightly or wrongly I eventually settled for a budget end Vichy VC99. Other than occasional replacement of the AA cells its proved reliable and sufficiently robust and accurate for my needs.
Quite so, Brian. Even though you had used a Fluke in your professional life you didn't rush out and buy one when you retired. You downgraded to something that has given you good service. That's what I want!
Hi Ralph. Thanks for sharing. After using a couple of capacitors and resistors and using your previous timing method I recently experimented with code similar to this new code. Previous methods served me well until I hit an issue with a troublesome encoder with an ESP32. I was surprised just how well it worked with both slow and very fast rotations with a very noisy encoder. I've placed most of the code in the interrupt routine, which I realize is not recommended but no issues so far. While interrupt pins are scarce on the Nano, as you say there are plenty on the ESP32 which is what I am mainly using now. I've stopped using the encoder modules, just an encoder and ESP32 built-in pullup resistors. In part 2 of Jack Ganssle's article you referenced there is an interesting statement, "Squalid taverns are filled with grizzled veterans of the bounce wars recounting their circuits and tales of battles in the analog trenches." That was a reference to hardware debouncing, but it seems to be true for software debouncing too :)
I'm very happy it's working, Garry, even with most of the code in the ISR. At least that way you are not dependant on the loop to detect changes. Yes, Jack Ganssle's paper is very good reading!
Just happened to have some of these that I thought would work well for a project I'm putting together, but all the other code I found either required hardware debouncing or required that the it be rotated so slowly that it was almost unusable. Thank you for putting this together and of course thanks to Marko Pinteric for his brilliant idea. It seems obvious in hindsight, but I could never have thought of this.
Indeed. If you use the same rotary encoder as me (Bourns) I can let you have a PCB for this project (more than one in fact). If you want some, let me know otherwise pretend I was never here 😲
@@RalphBacon At the price, probably. Having said that, I've got a couple of cheap meters that belonged to my father and are 10+ years old. Apart from blown fuses, they've never given problems. They seem to be manufactured down to even less of a price than the Anengs, but have still lasted OK. I guess *guaranteed* reliability is what costs a lot of the extra cash. On further thought...why not a good analogue meter? Probably as accurate as you need, and a wobbling needle can sometimes tell you as much as an oscilloscope. 😆
Thank you for this video - I've just found it after a week or so of investigating options for coding multiple rotary encoders (x5) in an Arduino based button box for sim racing. It will hopefully help me quickly extend a matrix-based wiring using Keypad.h and Joystick.h which interprets CW and CCW rotations as sequential button pulses (A-then-B or B-then-A) - so that I can interpret the rotations and output a single button push through the USB HID interface.
Thanks Ralph. Nice solution. You could go the whole hog and put the small amount of processing in the interrupt routine. It would allow the net count to accumulate in the background. I know the main loop could potentially have to deal with a count change greater than +/- 1 but that's not necessarily a bad thing. For those who only have a single interrupt pin, use a 2 input xor gate. I couldn't resist buying these single gate schmitt xors this morning (£3.75 for 50 from RS). Also good as inverters and the schmitt inputs allow for a bit of effective light filtering of noisy switch inputs.
Yes, that's possible (and desirable if the loop does a lot of work when we might miss pulses). Or put the whole thing into a separate task, that way we would never miss anything. Schmitt trigger chips work very well, I tried them and it was amazing!
As an embedded firmware engineer, I often encounter the strategy you've described for decoding quadrature signals. This involves using a state transition table, where each entry represents a state transition and its associated value indicates the count produced by that transition. Encoders typically follow a mechanical sequence that renders certain state transitions invalid, assuming the encoder is functioning properly. These invalid transitions are often detected due to switch bouncing and they yield no count. Valid counts, on the other hand, will output either +/-1, depending on the direction of rotation. Upon examining Marco's code, it seems that he generates an index by combining the current and previous states. While functional, I personally find this approach less readable. Therefore, I tend to create a 2D array that can be indexed using the current and previous states. Here's an example written in C: // Union type representing encoder input states as a byte typedef union { struct { uint8_t A : 1u; uint8_t B : 1u; uint8_t : 6u; }; uint8_t bState; }EncoderState_t; // Transition tables for X4, X2, and X1 resolutions const int8_t X4_TRANSISTION_TABLE[4][4] = { { 0, 1, -1, 0 }, { -1, 0, 0, 1 }, { 1, 0, 0, -1 }, { 0, -1, 1, 0 } }; const int8_t X2_TRANSISTION_TABLE[4][4] = { { 0, 0, -1, 0 }, { 0, 0, 0, 1 }, { 1, 0, 0, 0 }, { 0, -1, 0, 0 } }; const int8_t X1_TRANSISTION_TABLE[4][4] = { { 0, 0, -1, 0 }, { 0, 0, 0, 1 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } }; Here's how the encoder data can be processed in practice: static void ProcessEncoder(CCL_ENCODER_Encoder_t *pEncoder) { // Variable declarations int8_t chCount = 0; uint8_t bCurrentState = 0u; uint8_t bPreState = 0u; // Update state tracker and get current encoder state pEncoder->preState = pEncoder->currentState; pEncoder->currentState.A = pEncoder->pHal->pfnReadPin(pEncoder->pinA); pEncoder->currentState.B = pEncoder->pHal->pfnReadPin(pEncoder->pinB); // Translate states to byte representation bCurrentState = pEncoder->currentState.bState; bPreState = pEncoder->preState.bState; // Determine count based on encoder resolution switch(pEncoder->resolution){ case RESOLUTION_X1: chCount += X1_TRANSITION_TABLE[bCurrentState][bPreState]; break; case RESOLUTION_X2: chCount += X2_TRANSITION_TABLE[bCurrentState][bPreState]; break; case RESOLUTION_X4: chCount += X4_TRANSITION_TABLE[bCurrentState][bPreState]; break; } // Update position changes pEncoder->lastPosChange = pEncoder->posChange; pEncoder->posChange += chCount; return; } Note that the encoder's resolution is dictated by how many edges you detect. For full resolution (X4 mode), every edge of both inputs is detected. However, certain encoders feature mechanical detents but allow you to detect half detents. If you want to count only these detents, you'll need to adjust your implementation, which is why we provide transition tables for each resolution mode (X1, X2, X4). I noticed in your implemation that you only set a flag in your ISR, please keep in mind that if you don't read the encoder state in your onChange ISR, you might still miss transitions depending on the duration of your main loop and the speed of rotation. Personally, I call ProcessEncoder within the ISR, although this depends on your system's resources. Despite these precautions, it's possible to register incorrect counts if the encoder spins rapidly enough that the transition time falls below the switch bounce time. In such cases, other mechanisms are necessary or an optical encoder approach may be more suitable for achieving the desired speeds.
Nice. 👍 But missing transitions is not always a Bad Thing. My car radio just ignores my fast spins and forces me to take a more laid back approach. So rather than trying to keep up with some idiot user (in the car case, me) spinning the volume control up or down manically, it just ignores those pulses until I slow down to a reasonable level. I wouldn't advocate doing any processing to speak of within the ISR. Although if you used an ESP32 you could fire off a task (even on the other core, with great care) to do whatever processing you need triggered from the ISR.
Yes, FUBAR! I still remember when my Dad taught me all three (I'm 60+): SNAFU, TARFU & FUBAR. SNAFU: Situation Normal, All Fouled Up. TARFU: Things Are Really Fouled Up. FUBAR: Fouled Up Beyond All Repair (or Reason). Words to get you through any day! And Foo Bar came from the comics of the time. Since the actual use of FUBAR wasn't allowed, they simply replaced it with FOO BAR, which was allowed (it was often written as "FOO" & "BAR" around the edges and such...). And that is where the FOO in FOO FIGHTERS came from. They were those sightings of aircraft that did weird things in flight: They were FOO.
And just to close the loop, so too speak, the Foo Fighters (USA rock band) took their name from those UFO-type foo fighters. You're a font of knowledge, Philip!
I bought a Owon B35T+ specifically because it had bluetooth for two things - realtime display if you have to have the meter somewhere you can't see it (i.e. remote meter) and because it supports data logging... which I think the ANENG partially supports the concept of - as it has the graph. What I really liked about the B35T+ was that it has so-called "offline" data logging, where it could be set to record X number of measurements, Y times per second, and then you could walk away, and then download the log over bluetooth later to see what happened during the session. However, it's over double the price of the ANENG, and unless you need the datalogging, I think that's probably all the meter you need, and has it's own bells and whistles ;) And I love Marko's technique to eliminating debounce... I basically see it as a state machine, whereby you lockstep through the states, meaning out of sequence is invalid, and only the next mechanically possible sequences is valid... nice! :) I've always though the wait and other techniques to mitigating debounce were pretty poor... but not as poor as certain manufacturers shipping products to end users without any debounce at all... we have a microwave with a rotary encoder you can't turn fast else it goes backwards :D
Your microwave issue is exactly one of switch bounce and missed pulses. The manufacturer did not test it enough! If they had used Marko's method they (well, you) would not have that "backwards" issue. Thanks for the info on the multimeters, I've now got a short list so we shall see soon what I end up with.
I should point out that there are dedicated IC for button debouncing. Maxim has MAX6816 & MAX6817 (single & dual switch, fixed 40 ms delay, 2.7V to 5.5V supply). Onsemi has MC14490 (6 switches, programable delay by a single capacitor, 3V to 18V supply). And there could be more. A bit expensive (MC 14490 is 5,56 € on Mouser, MAX6817 is 4,16 €, MAX6816 is 2,69 €), but if you want reliable debouncing with ESD protection (industrial setup?) AND your code has its hands full (already allocated HW interrupts, hugely input dependent timing for the main loop, etc) AND isn't reasonable to dedicate a uC for the buttons, then you may consider a debouncing dedicated circuit. Look, I'm not afraid of writing code, 30 years ago I was writing debouncing code in ASM, but sometimes a better tool for the job is not always in the usual toolbox.
Indeed; I tried out a simple Schmitt trigger circuit (costs pennies, from Jack Ganssle's paper, I believe) and it works very well. The trade off, as you summarise very well, is whether we want additional hardware components or are prepared to do it in software (the accountants will insist on the latter, of course!)
@@RalphBacon HI Ralph, I've had the Aneng for a couple of years now, not used daily, but quite often. the stand is a bit naff, but used flat it's fine.Of course you will need better leads, but they are standard shrouded 4mm. As you are not looking at the same model, I'll not go into too much detail, suffice to say that I have used some very high end meters in my time ( including the ubiquitous Flukes) but this is probably one of my favourites.
Thank you Ralph! This code (and a bit of help from another comment string in the comments) got part of a little project I'm working on working great without having to go over to a more expensive (and not really necessary for this project) optical encoder.
@@RalphBacon That is very true. Question for you, any chance you know why it seems many other functions if they are outside of *if statements* seem to cause problems with the encoder and this code? There was a comment string below where someone was having an issue with printing to an OLED and it turned out that as only as the print calls where in an *if statement*. I had that same problem initially too but with and I2C 16x2 LCD which putting the print calls in *if statements* corrected the issue with the encoder counting and missing counts. The problem is, as I add other code for other aspects that have nothing to do with any printing starts to do the same thing to where the encoder changes get missed by the system. and I have to apologize a head of time because I'm sure I'm not explaining it as well as it could be and could be using incorrect terminology, I am pretty know to the whole world of microcontrollers and coding for them.
@@RalphBacon Hi Ralph, thank you for your acknowledgement and inquiry into my question / problem. Hopefully I can explain it well enough, as mentioned previously, I am new to the whole coding microcontrollers so I might be using wrong terminology. Anyways, here is two sections of the code in order as I have them in the loop so to possibly help /////////////////////////////////////////// //Reads Serial Data from Scale and Prints// ///////////////////////////////////////////
if (startState == LOW){ currentWeight(); lcd.setCursor (8,0); lcd.print (currentScaleWeight); lcd.print(" "); lcd.setCursor (15,0); lcd.print ("*"); lcd.setCursor (15,1); lcd.print ("*"); } if (startState == HIGH && coarseFine == HIGH ){ lcd.setCursor (8,0); lcd.print ("FINE "); } else if (startState == LOW && coarseFine == LOW){ lcd.setCursor (8,0); lcd.print ("COARSE"); } Originally, I had the currentWeight(); function outside of the first *if* statement and that seemed to be causing the encoder to miss counts unless I turned it really slow to where it was a very slow and controlled fall into the next detent of the encoder. I only had the first if statement to chance the display to show the current weight data from the scale instead of the COARSE or FINE so I chalked it up to the reading serial data from the scale (which is a software serial read, not hardware serial if that matters) was causing it because when I put it in the if statement so that it would only read that when the startState was pulled low by the "RUN" button I have on the panel, it seemed to work fine. I however then realized that I wanted it to change back to showing which adjustment mode I was in when the startState when back high (i.e., stopping the run mode) so I wrote that 2nd *if* and *else if* to handle that and that is when it started acting up again to where it would miss changes from the encoder unless I turned it really really slow. Could it be that it is staying stuck with the currentWeight(); function running and if so, is there a way to force it to be canceled? I though that when startState when HIGH that it would automatically end that function. Am I wrong here?
Are you using interrupts to detect the rise/fall of the rotary encoder, as per my demo? That is, have you got an ISR function that is called whenever the rotary encoder value on one of the pins changes?
I brought a multi-meter a few month ago from Mustool. I wanted a High current capability as well all the standard measurements. It was a good price and I am very pleased with.
I've certainly heard of Mustool, but they didn't appear in any reviews I looked at. As I don't need high current capability (10A occasionally is more than enough) I'll keep looking, thanks for the info.
Thanks for this ... it was the solution to an issue I was having. A while ago I put together a test setup to learn about Arduinos (Mega & Nano as well as ESP32) and have a number of breadboards containing various bits of circuitry connected to LCD displays, LEDs, Temp sensors etc as well as one connected to a Servo & a Stepper. Using the Arduino IDE examples as a starting point I created code to read a Pot and use it to set the position of the Servo, and a Rotary Encoder to set the position of the Stepper. It basically worked OK, but had issues with the Servo chattering and the Stepper behaving weirdly. I found that the 5V supply rail was noisy and after fixing that, the Servo was OK ... but the stepper was still acting weirdly. It seems that it was indeed switch bounce - I modified my code to use the method you describe here and no more issues.
Ralph: I'm in the process of learning how to wire and program and Arduino for a project I'm working on. I've watched a few of your videos, and I must say "Awesome!". Everything is very well explained. The camera shots are excellent. The editing excellent. Thank you so much! I will probably have only one Arduino/Stepper motor project. I'm building a Power Feed for a Bridgeport Milling Machine. But it still requires me to learn all about Arduinos, coding, switches, potentiometers, encoders, etc. So, thanks for your help. Regards.
@@RalphBacon Thanks Ralph. With the help of you and several other TH-camrs tutorial videos, and, believe it or not, Google's AI Bard, I have gotten a big part of my project working. Just need to add a Rotary Encoder to make steps on my Stepper Motor. Thanks again. Regards.
@@RalphBacon Speaking of components, I bought one of those electronics kits with an Uno, breadboard, switches, and a host of other parts, to see if I could learn this stuff. And it was ok for that. But I would classify most of the parts as not the best quality, and to do some serious, regular work, some better quality parts are needed. Parts that actually FIT in a breadboard, and are not complete crap. Yet, I don't want to spend 10 or 20 times as much for a part, if a decent quality one is available for less. The project I'm working on is an industrial machine, that is now used in a hobby setting. But I want reliable parts that will hold up. I'm not interested in some of these "toy" projects that are often seen in TH-cam Arduino projects. Can you help with recommendations for parts and sellers of them, that fit the above described requirements? Or point me in the right direction? PS: I'm in the US. Thanks
OK, you are correct inasmuch that kits of parts, whilst educational, are not fit for actual project use most of the time. Reputable suppliers such as DigiKey, Mouser, RS Components, Farnell will supply better quality components - with a guarantee behind them. That said, most parts are made in the Far East and you, the consumer, must therefore sift the wheat from the chaff. Ignore those that are selling quantity over quality, or aka building DOWN to a price (hobbyist/educational stuff that you've had experience of) rather than those building UP to a particular standard. Remember that iPhones are mostly built in the Far East but use quality components and have great QA attached to them. They must source their components from somewhere and there are doubtless a few warehouses of "surplus" Apple-designated stock that you can tap into. It's a minefield but it's good that you are aware; spend some time on the above mentioned suppliers and see what you can find (but take a deep breath when you compare the prices too!).
@@RalphBacon Thanks Ralph, for taking the time. I appreciate it. I do buy most of my electrical parts, such as Relays, Contactors, and Switches, on Amazon and have had no problems with imported parts. But when it comes to electronic parts, I just need to differentiate between the toy parts and the Big Boy parts. Thanks. Regards.
You can get optical rotary encoders for free from printers or computer mice (scroll wheel). At least for experiments, as you will usually get the encoder wheel and the sensor as two separate parts and not a single device with pins. But it is still very easy to mount them in a way so that they will work. The printer ones have a very high angular resolution. I remember having used one mounted to a motor shaft for constant speed control using an AVR. It has been a long time ago (so long that Arduino wasn't even a thing ;) ) and don't know what I actually wanted to use it for, but I know that it worked very well (I also implemented a PID loop for more stability). But I am planning to use one soon (more likely from a mouse wheel than a printer), because I do want an MPG (manual pulse generator) or jog wheel for my CNC. Sure I could get a complete MPG for 30€ from ali, but where's the fun in that! ;)
Excellent repurposing idea there! But quite a challenge I would have thought - then again, you've done it before so your experience already tells you what to expect. And you save $30 from Ali!
@@RalphBacon As it was pre-my-workshop times I remember having built the frame for my test setup with "Fischertechnik" (don't know if that's a thing in the UK) and clamped the motor, encoder wheel and sensor to it. Just to give you an idea how fiddly it is, i. e. not at all. The sensor itself is just an IR LED and two phototransistors. With pullups it is pretty much a drop-in replacement for a mechanical encoder. No software changes needed at all (except maybe that the pulse rate can be much higher becuase of the higher resolution).
the sharing to phone function is handy for logging data over a period of time, screenshots and you can put the meter on the circuit you want to measure which can be in another room and read the data of your phone without the need for another person holding the leads on the circuit, it's the ultimate isolation so it's safer for the user, there may be more what makes the Bluetooth connection handy.
Nice vid Ralph. I have done battle with these cheapie RCs and yes they are noisy as old Harry. I am going to have to investigate this new algorithm. I like clever debouncing, Gansells methods for normal switches are really good, just shifting bits into a register running all the time then when you want to check your switch, you just look at the value of the shift, all zeros one state, all ones the other. Any mix and you can safely discard it coz it's bouncing. On the subject of DMMs, I bought a fluke 10 for 70 quid back in 1992 and it is still my daily driver today. They seem expensive but you are expecting to spend £30 every three years, my fluke hast cost a lot less in the intervening 30 years. Quality wins in the end.
Then again, I thought that buying a Brymen for $100+ was going to be a good investment for at least 5 years, and hoping for 10. Failing after 2 years is unforgiveable. What if a Fluke did that?
@@RalphBacon Then that would be a good one for you, I love mine. I bought it as a second or third? lol. meter but I use it all the time it's my favorite.
The only thing I found with the cheaper multimeter was some odd mA range choices. With rotary decoding, if you add one quad nand gate chip (74HC00), you can do the decoding using only one interrupt pin as an input and one more pin set as an output (any pin). This can save the second interrupt for some other use. The interrupt is set to change of state and the output pin controls which of the two input lines goes to the interrupt pin through the nand gates. The sequence would be, wait for interrupt, change the state of the output pin, read the interrupt pin state and the output pin state (for the movement) and then leave the int routine. The next interrupt will occur when the other input from the encoder changes, it will ignore any further changes from the original pin. Wiring: each output from the encoder goes to one input of a nand gate (two gates used). Both nand outputs go to nand gate input on a third gate. Output of the third nand goes to the interrupt pin. Control pin goes directly to one of the encoder nand gate second inputs and is also inverted by a 4th gate (wired as inverter) and goes to the other encoder output nand gate input. In this way, the output pin selects which encoder output goes to the interrupt. You get an interrupt, you switch to the other encoder output and you read it's state and the current output pin state (both tell you which way the step went, clockwise or anticlockwise). You then wait for the next interrupt and do the same. Two encoders to be connected to the two interrupt pins independently.
Get an old and used Fluke from your local auction site. Excellent value :) Keywords for a cheap optical AB encoder seem to be "Photoelectric Encoder Speed Sensor" marked with TTLH and 4 wires. these are about $4 altough they require the DIY of an axle and a knob to make it into a proper encoder.
It has been years since I wrote an ISR, but I think the first line of code in the ISR always disables the interrupt. You don’t want your ISR interrupted while it’s servicing the interrupt or you risk overflowing the stack. Then, the last line before you return should reenable the interrupt. I love your series of videos. Thanks for doing them.
Hey Steve, thanks for the kind and supportive words. I'm glad you find my videos enjoyable 👍 Regarding the ISR, on the AVR (Arduino-like) modules my understanding is that the ISR won't (cannot) be interrupted (they are _automatically_ disabled upon entry to the ISR) but one further interrupt (or the conditions that make it happen) can get queued. After that they get discarded. So recursive or runaway interrupts don't happen (thank goodness). On the ESP32, with the Arduino framework, it is a similar situation; but natively (using the ESP-IDF) you can get runaway interrupts (followed by a system crash). 🤦♂️
I have always thought the Rotary Encoder to be a very versatile UI control. It can be used to scroll through a menu (with a display, obviously) and the PUSH_BTN serves to select the new mode. If that mode is an adjustment, one uses it as you have done (or intends to) with temperature control. Another press of the PUSH_BTN 'fixes' the adjustment and returns to the menu. Some selections on the menu may display another menu and so on. I wouldn't suggest this for, say, a word processor but the above demonstrates the versatility of the Rotary Encoder. Now to the intriguing algorithm in the video. I immediately, wondered, whether this could be achieved with a few logic gates. Yes, I know one has a MCU so let that do the work but a hardware solution could be designed as a daughter board that could be used in any project. Just running it up the flag pole to see who salutes it. Great video, Ralph and my condolences on the loss of your multimeter I have an old AVO that still works after over 50 years, if you need it
Funnily enough, Michael, I have a 40 year old analog meter that still works just fine, but not having an exact figure displayed is very disconcerting to me! Logic gates for the rotary Encoder. Hmm. If I had the time, I'd accept the challenge. But as it happens...
Poss. +'s of broadcasting to phone... Can take screen shots Can show someone the readings who isn't able to see the DVM (possible remote) For video&audio recording/archiving purposes.
Lots of reasonably priced meters out there - just check the weight of the unit. Nothing more annoying than the thing shifting around as you are trying to get the probes into position and needing to add bluetac to keep it stable. Happy shopping!
I am definitely going to try this code. One reservation I have is that there is a possibility that there will be time between the interrupt and the processing thereof, because it is done in the main loop. This could potentially cause delays and missing clicks…
Agreed that any time-critical processing done (or detected) in the main loop is going to risk missing an external signal. You could modify the sketch to have interrupt(s) on the rotary encoder pin(s) which would ensure their detection, if not the actual processing (still done by the main loop). On an ESP32 you could easily write a task to do the processing of the rotary encoder clicks along with an interrupt for the detection thereof; obviously such code would not have been suitable for a demo but I often put stuff into tasks to get them out of the main loop, especially when they are long running (or have delay😲 statements in them).
@@RalphBaconThanks you for replying Ralph. I’m gonna test the sketch as is first, and when it works (especially for some stepless rotary encoders I mistakenly bought) I will try to fumble it into the ISR routine…
And: it works flawlessly with those darn stepless encoders as well! Very, very pleased with that code! I also managed to incorporate it in the interrupt routine! Thanks again Ralph for sharing this find with us. Might you be interested in the ISR incorporated code, let me know.
The "RotaryEncoder" library by Matthias Hertel uses this technique (and later wrote my own just for fun :) ). I think one pitfall of using interrupts is that you can get many spurious interrupts by the switch bounce. I've used a combination of both hardware (filter) and software (state technique) with interrupts for good measure.
Hardware is certainly the 100% answer. I can't remember if I did a video on this (no, I'm not watching old re-runs) but in my experiments it was fantastic. Just half a Schmitt trigger chip and the problem was gone!
23:30 Optical rotary encoder will do a switch like bounce if there is a voltage drop sufficient to cause the output transistor to flip flop between hi-lo state even when there is no movement. It is an out of voltage spec situation.
@@RalphBacon Optical rotary encoders tend to operate in 5-24V range. Micro-controllers work down to 3V and continue to process the rotary encoder dropouts (electronics do not hold a proper high and waffle states). I have done spinners and they work fine alone but adding few led buttons is enough to get a variable voltage drop (mouse jitter on windows screen). It also does not help when the Optical rotary encoder use 1.5 - 2m leads (encoders are industrial use on feed rollers for distance measurement device with fix diameter wheel) and hey why hack off the extra - dah!
You should go for Fluke. I have two of them, over 15 years of use on the newest and well over 20 on the original one (Fluke 185) and still working perfectly. Tektronix took over the DVM range for a while so my second one is a TX3 although it's identical apart from the colour of the 'sleeve'. Serial optoisolated output to PC. You get what you pay for. Can I suggest using 1.5V LITHIUM Rechargeables to avoid 'battery black terminal rot'.
Fluke is a name I hear and read about a lot. And for companies that depend on their meters working 100% with excellent repair and recalibration support I totally agree. For hobbyists, though, a $20 multimeter probably is "enough" and easily replaceable when (not if) it goes wrong. That's my 2c anyway. 😲😉
@@RalphBacon the AN870 multimeter arrived. Checked it against my voltage reference and pretty much bang on the money. The only thing I'm regretting is that I ordered a green one and not a red one. Hey ho. I guess that I'll get used to it LOL!!
As for the rotary encoder, I 'borrowed' some of your code from your previous vid for my motorcycle ignition simulator on an Arduino NANO. I twiddle to adjust the simulated RPM. Works perfectly so I see no need to change it, but no doubt I will take a peek at this new code and see if there's any reason that I need to use it.
I saw many good critics and tests for the Aneng AN9002 on youtube etc. So I bought 2 of them on Aliexpress last year. They work fine and do everything I need. In fact they are much better than you really need for most things when working with yC and digital electronics. Who really cares if your contoller gets 3.297V or 3.3V and if a resistor has 11.001 kOhm or 11.002 kOhm? In most cases it's not the multimeter but they way the measurement is done, what causes the bigger mistake.
Suggest that if you want to buy another or even a diff DVM, make a collection of resistors and measure them on day one. Keep the resistors as a calibration set. Could measure their resistance periodically.
Interesting video... Why not use a RC low pass and feed it into a schmitt trigger and thence to the uC ? Some uCs have ST input pins, so that can be eliminated as well. SMT components will not increase the PCB size hugely , I reckon. I do this for tact switches.
Yes, I've used SMD Schmitt trigger ICs to achieve this too - but it does complicate the entire circuit to a degree. But it did work 100% without any software intervention at all.
Quite interesting, thanks for sharing! I personally now use a timer interrupt for inputs, basically hardware polling, with a little debounce counter that waits until the reading is stable for X samples and sets a flag. Pretty much stole it from AVR freaks and re-wrote it for clarity. I agree on the Fluke statements, I bought a couple of them, and paying that amount of money and get cheap-ass PVC leads is just plain ridiculous. Never again.
Yes, your interrupt routine is (yet?) another way of doing it. We all have our different ways, I suppose. Flukes are overpriced (like BMWs or Mercs). Cheaper units perform pretty much as well.
@@RalphBacon yep! In the end as long as it work for one self, that's the important part :) I'm sure going to have a look in depth at the one you described when time permits!
If the meter isn't damaged, I'd maybe consider getting it calibrated but if it does this again in less than two years then I guess that wouldn't have been worth it. I think a meter should last longer than two years though. My UT61E came spot on (tested against a calibrated power supply and I found one value where it disagreed by 0.0001 V) but I haven't tested it recently. Uni-T meters (including the E+) have a relatively high burden voltage and are somewhat vulnerable to ESD applied to the probes according to testing by TH-camr joe smith. The high resolution is nice for being able to see whether a value has settled but otherwise unnecessary for me and it doesn't have a backlight either. I wish it took AA batteries too. PP3 batteries aren't very good value for the energy they store and I prefer rechargeables anyway.
So, reading between the lines, Sean, you reckon your UT61E meter is pretty good but ultimately has more features (or better accuracy) than you needed to pay for? Pretty much what I said in the video 😂
Hey Ralph. Thanks for "De-Coding" Marko's code. I'm going to try your version soon and then see if I can get it modified for my project. I've watched this video several times and glean a little bit each time. Thanks! Regards
So ..... fortunately I had picked up on you demonstrating this in ESP32, as I assumed at first this was Arduino code since you are the "Arduino Guy" and the video thumbnail said "Arduino". It didn't compile and since I'm an Arduino Newbie of only a few weeks, at first I didn't know what the problem was. I did however, figure it out and got it running. (You should be proud of me, Master!) I am however, still trying to make it run my project code if it's turning CW or CCW. (ACW?) instead of just Serial Printing it.
Done well, you have, my young padawan! Instead of just serial printing it, why not update a (global) variable that has either +1 or -1 (depending on rotation direction) that your main code loop can pick up on and take the appropriate action (as well as resetting the global variable to 0)?
@@RalphBacon Thanks to your help, Master, "Grasshopper" has gotten this code working in my project, and all other aspects of it working as well. Project complete! I will email a copy of the code and a description of my project, etc. It's pretty cool. might be worth a video! Regards
If you can't justify the cost of a new Fluke meter, they can be great value used. I picked up a Fluke 27 in perfect condition a few years ago on eBay for £25 (plus £6.95 postage). It's accurate, super rugged, waterproof, drop-proof to 3m and it is incredibly stable and reliable. The great thing about Fluke is that they are utterly dependable. Forget about meters failing after 2 years, this will still work perfectly after 20 years.
Totally agree; but I think you were very, very lucky with your find. I don't want to spend $209 on a new multimeter when a $25 will do the job, last 2 years (maybe) and I can then upgrade to a newer, just-as-cheap model in the years to come.
@@RalphBacon I was lucky, but you can still get them and other used Flukes for £60-70, or less if you are prepared to wait. The problem with a $25 meter is that you can't necessarily rely on it even for the first two years. I have a slight test gear acquisition addiction and picked up an Aneng 8008 fairly recently. I used it a lot for a while since it was small and handy, but within a few months I started getting occasional weird readings which became more and more frequent. I think there may be a dry joint or a faulty component on the PCB. I've had similar issues with other cheap meters. A meter you can't trust is worse than no meter at all.
Hi Ralph About a year ago I had some discussion here about video #19 and interupts/rotary encoders and using one or two interupts on a nano/uno. Since then I have bought a Nucleo F401RE board. It uses a STM32F401RE MCU I think that's the same as the Black PIlls. Now while your "old" #19 code worked fine on a nano with the F401RE board I just could not get rid of bounce. I tried altering the 5mS wait from 1mS up 10mS but I could not get rid of bounce. I assume the much faster MPU must be able to service multiple interupts coming from multiple bounces in under 1mS. With the F401RE any pin can be an interupt pin so I was eager to try your new #226 code with the F401RE It objected to the use of a volatile boolean but once I changed it to an normal boolean it worked fine. In any case with a 32 bit MPU it can access an int in 1 clock cycle so I cannot see any need to use volatile variables. So I can confirm the code works fine on an F411RE board. These Nucleo Boards are worth doing an entire video on. Regards Dave
I'm glad you got it working on the F411RE board but I must caution you against not using a volatile variable inside the ISR. The use of "volatile" simply tells the compiler that the value of the variable might change "behind the scenes" (by an interrupt, for example) and that therefore any previously read memory value (for that variable) now stored in a register _cannot_ be relied upon. The compiler should therefore issue code to _always_ retrieve the value from memory and not rely on any register value for that variable. What sort of compiler objection did you get?
I think the Bluetooth interface back to the DMM could be useful when you're running long tests and don't want to sit in the lab all day while you monitor a voltage, current, temperature etc. Just keep your phone nearby and keep an eye remotely (within the 10m or so that Bluetooth serves).
Good point! I guess you never know how useful something is until you have it available. Like air-con in the car. A larger workshop. Ketchup. The list goes on.
If the phone app is decent, you can probably do logging with it too. I work as an engineering tech, and we have some of the big older Flukes that do data logging, and then you upload it with a USB cable. There are cases when you really need to log data over a long period of time, or watch a circuit to catch when it glitches. Also, just in the thumbnail on the ad page, I noticed that it's showing an analog display. Sometimes that could be really nice as well. Easier to see if something is trending up or down than watching the flashing dots on the meter.
Hi Folks, Use interrupt on the clk pin, and add a 250nF cap on the clock and dat pins. Also I changed the 10k resistor to 2.2k on the pullups. This Will give you a great starting point. Software will only make it more reliable.
@@RalphBacon The capacitor slows and somewhat smoothes the transitions (it is an analog filter). The downside is that the voltage will be in "no man's land" (between logic high and low) for a while, and that can confuse digital inputs that don't have Schmitt triggers. The resistor and capacitor values need to be tuned so that the voltage across the capacitor charges/discharges slowly enough during a bounce so that you don't see a change in logic value, but fast enough that it responds to a legitimate change in state. With these cheap encoders, that can be tough since they can bounce badly, but you still need to detect pulses at tens of times per second (when spinning the knob quickly).
I found that the falling edge is quite sharp. The caps keep the noise below the active high threshold of the digital io. I did some tuning on my scope and found that even the fastest spin of the knob had falling edges about 1ms apart. The only software filtering i had to do was to reject any falling edges less than 1000us apart.
The rotary switch on my UniT Clamp Meter started giving trouble after around 5 years. Apart from being left in the garage which I guess could be damp it was never mistreated. I was able to take it apart and fix the issue though so it's still going strong.
Hi Ralph ! as a beginner, on a nano, i uncommented void IRAM_ATTR rotary() but can u explain why for line 78: attachInterrupt(digitalPinToInterrupt(PIN_A), rotary, CHANGE); 'rotary' not declared in the scope ? thanks a lot
Im also a beginner and i have the same problem "78: attachInterrupt(digitalPinToInterrupt(PIN_A), rotary, CHANGE); 'rotary' not declared in the scope" cant figure it out :(
It means that it is expecting a function declared called 'rotary' that will be called whenever there is an interrupt. I guess you have either mis-spelled it or have not written an ISR called 'rotary'?
As far as meters go, unless you need extreme precision, my policy is to go with the cheapest one that gets the job done. As long as it meets your accuracy requirements, there's really no reason to but a $50 meter where where a cheap $10 meter will do the same job (prices relative). With the current state of technology, most meters you buy today, regardless of the price, will give you pretty good accuracy for most purposes.
I'm with you, Jason - most of the way. I don't think a $10 meter will cut the mustard, but a $25 meter certainly seems to offer a lot. There's a question about _mechanical_ reliability (that centre knob gets turn an awful lot) but we shall see!
I made a volume knob for my pc with an arduino and rotary encoder(a couple years ago). I don't use it because it makes too big of a change in volume per click. I'm gonna have to revisit it now and see if I can make it have finer adjustment. Maybe this code will help. Thanks!
Unfortunately, Marko's code (the one without interrupts, that is) will trip with delays when the value returned is 0. Normally you wouldn't do anything if the encoder read 0, so no problem... except if you want to blink a value being edited by the user. Which is how I discovered this little problem. My code reads the encoder, updates de value if != 0, BUT, 0 or no 0, it will still do other stuff, namely, showing/hiding the number being edited on a 7-segment display every 1/2 second. As far I checked, a delay(5) will break the encoder state, but only if it returned 0, if it returned 1/-1, then the delay doesn't affect it.
Yes, this has been mentioned before, San; even his original code would / could "miss" a transition if there was a delay in the loop. My choice of using an interrupt for detection has not improved things without doing more work in the ISR (which is not ideal). For an ESP32 I would put the entire thing into a task which could track the rotational count for the main loop to pick up on. The downside there is that the rotation could "jump" from 10 to 12, for example, if the loop was busy or had delays. Ideally the task would handle all aspects of the rotary encoder including inc/decrementing whatever it is that it is being used for. Always a challenge!
@@RalphBacon Yep, although in this case, strangely, the encoder looses track of the situation and never recovers. I haven't got the time to investigate why (the internal state no doubt), but when it misses a step, it misses them all afterwards! :)
Arduinos have "pin change" interrupts for all port pins, but your service must identify the pin that changed. You can mask off the pins that are not connected to encoder(s) so that their changes do not cause interrupts. This allows up to four counters in 8b ports. I can email you some code if you like.
I've used pin change interrupts, Byron; but I'm convinced I would just totally confuse beginners unless I used a library to abstract the complexity away. I think hardware interrupts are much easier to configure and understand, initially, at least.
It's can be worth looking around for deals on Fluke gear. I picked up a 115 with a soft carry case and a set of accessory probes & leads for £115 in 2016.
The problem is, Michael, that I don't want to spend that sort of money in this climate of rising gas prices and meters that go foo bar in 24 months! No matter how good a deal it might be.
for all ppl using arduino and getting the error "Compilation error: expected initializer befor 'rotary'" in this line "void IRAM_ATTR rotary()" just delete IRAM_ATTR, this is for ESP only. just use "void rotary()"
It seems your thumb is healed up well. Don't argue with a knife. (Ralph "You so doll that you can't cut butter". Knife "I show you doll". SPLAT. LoL Great work on the encoder code by you and Marco. 3 years back, i got myself a UNI-T 61E, it is far better than i need right now -but what do you need in years ahead? My 2nd multimeter, actually my first, is a 30 years old Matex 3½ digit. It does all the grunt work. You have a very good point that most people need to learn -what do I really need and what can i afford, do I need that bragging right. You could ask on the "LearnElectronic" channel, he has a few multimeters and seems to know the good and the bad.
I'll check out that channel you mention, thanks Flemming. And yes, my thumb is healing, last (nurse) dressing yesterday, now I just have to do it myself. And no more playing with craft knives (dull or otherwise).
working on a bench, the BlueTooth isn't that handy but imagine you are testing a receptacle in the room adjacent to your fusebox. You can hook up your meter and use your phone to remotely observe readings while you connect and disconnect the circuit. No idea what the BlueTooth range is but it could be handy.
Two years is far too short for a meter's lifespan. I've got a digital meter bought at the turn of the millennium (edit: for ~$150 from Radio Shack) that still works perfectly. I have 2 analog meters I inherited from my grandfather which are older than I am. It might be worth reaching out to Brymen and seeing what they say about your meter, presuming you haven't already. If all it needs is a calibration, it might be worth doing (and then you'll have a calibrated instrument!). Dave Jones of the EEVBlog (who sells his own Brymen models) has done a number of shoot-outs in the multimeter space. Aneng is one of the cheap ones that (pleasantly) surprised him, though I think it was a model 8008 he tested. Edit 2: That's a neat way to handle debouncing. Great Scott takes this a step further. If you're using "change" interrupts, you only really need one interrupt pin. Anytime that pin changes, read both pins. Use that to figure out how far the encoder has moved, in which direction. Also, now you still have an interrupt pin available for something else. 👍
Good points, Ted. I, too, have a couple of multimeters (including a 40-year old analog one) that still work perfectly. But I'm assuming that new stuff is not built to last otherwise how would they keep selling this stuff? I'll have a look at Great Scott's work too. It seems everybody and his dog (well, not mine, he's more a MicroPython dog) seems to have invented a new way of doing this!
@@RalphBacon Could well be; engineered obsolescence is certainly a thing. OTOH, multimeter tech is pretty mature at this point. I dunno. Watching the results of the chip shortages, ewaste issues, and various logistics nightmares, I'm hoping the pendulum swings back toward repair a bit. LOL @ uPython dog. 🤣 I think mine must be an Assembler, given her brute force.
That is a great piece of code. Thank you for sharing. I adjusted the code a bit for an arduino uno and extended it to increment more with a faster turn. // Has rotary encoder moved? if (rotaryEncoder) { // Get the movement (if valid) int8_t rotationValue = checkRotaryEncoder(); // If valid movement, do something if (rotationValue != 0) { int interval= millis()-lastMillis; //interval = time between 2 correct cycles in milliseconds. lastMillis = millis(); //set lastMillis to current time if (interval > quickTurn){ //int quickTurn is set tot 90ms //value + 1 incrementSize = 1; //int incrementSize } else if(interval> superTurn){ //int superTurn is set tot 40ms // + 5 incrementSize = 5; } else{ //if another turn happens in less than 40ms after the previous one. //+10 incrementSize = 10; } rotationCounter += rotationValue * incrementSize ; if (rotationCounter < 0){ rotationCounter = 0; } else if(rotationCounter > 100){ rotationCounter = 100; } // Serial.print(rotationValue < 1 ? "L" : "R"); // I had no nead for the serial. // Serial.println(rotationCounter); showValue(rotationCounter); } }
Hi Ralph, thanks for the excellent work. Just wondering about the pins you have assigned (32, 4 and 16), none of which are interrupts. I thought you mentioned pins 2 and 3 in the video. Also wondering about the line that mentions the push button doesn't have a pullup resistor. I do have 3 resistors on the KY-040.
I'm guessing, Jacques, that if I'm using pin 32 then it's not an Arduino (ATMega328p) chip, but is probably an ESP32 device. On the ESP32, _any_ pin can be an interrupt pin. Unlike the Arduino Uno (or Nano) which only allows pins 2 & 3 as hardware interrupt pins. Has that clarified things?
@@RalphBacon Thanks, Ralph. Yes, this makes things clear, except for the pullup resistors. The resistors are on the KY-040, therefore I don't understand why any device, ESP or Arduino, would need to connect internal pullup resistors to the pins. I noticed the pullup resistors in the original code by Marko Pinteric but couldn't explain why he has added them to the code. Would you know why he did that?
Yes, I do. 😁 You want more? Although my HW-040 (aka KY-040) modules do have pullups on the underside of the board, a bare Rotary Encoder will not. Using the built-in pullups of an Arduino etc will ensure that the code works. If your board has pull-ups then you don't need to make the GPIO a pullup. Otherwise you do. It would probably work with both, actually, as we're bringing the pin A or B LOW so it should be OK even if there is a 10K pullup on the board and an internal 47K pull up (total 8k2Ω).
@@RalphBacon Thanks again, Ralph. I'll give it a try on the Arduino Mega, which has 6 pins that can be assigned to an interrupt (2,3,18,19,20 and 21), although in real life it's closer to 4 pins since pin 20 and 21 are often occupied by components that need SCL or SDA. I have not used interrupt procedures before and I hope that connecting the KY-040 to pins 3, 18 and 19 on the Mega will not cause issues with interrupt signals coming from pins 20 and 21, to which I have already attached a pressure sensor.
Update: After deleting IRAM_ATTR from your code and adjusting the speed to 9600 baud everything works fine, until I try adding an OLED screen. For some reason the OLED screen slows down everything else. I can't get my head around it. The OLED screen print command is within the interrupt routine, simultaneous with the serial print command. What is Arduino waiting for before or after the oled print command?
Hi Ralph, I have dowloaded and compiled the sketch but get the following error message "Compilation error: expected initializer before 'rotary'" Errot points to line " void IRAM_ATTR rotary() " I am using a Arduino Nano and the IDE V2.1.0 Are you able to help? Rob
Thanks, Ralph. I just gave the code a spin and noticed that the first "notch" of each direction change is dropped. So the first turn when you start the code, then every subsequent direction change after that. In fact, you can toggle back and forth one notch left then one notch right and get no reading at all. I haven't dug into the code yet to try to code around that but was wondering instead if this was your experience too?
Hmm. That doesn't happen to me, not at all. I would suggest putting in a few Serial.print lines to make sure the change is being noticed. You can even put one in the ISR, you have my permission!
@@RalphBacon Hi, Ralph. I'm still doing my testing but I have narrowed things down to the microcontroller rather than the rotary encoders I was using. Tests on a genuine UNO and third party Mega2560 worked flawlessly per your video. The FireBeetle 2 ESP32-E that I've been trialing is where my issue lays. I thought at first it may have been because I was using interrupts for the WiFi connection however your unedited sketch saw the exact same result of skipping the initial turn, and subsequent direction changes. I post my findings once I get to the bottom of it in case anyone else comes across this issue themselves. Cheers for all you do.
Quick note: I used an ESP32-based board in my demo, no issues. I will look at the FireBeetle to see if it's any different. What pins are you using for the two interrupts?
@@RalphBacon Note that there's two verions of the FireBeetle; I'm using the newer model -E. I used pins 34 and 35. Something I observed is that the Arduinos correctly register all four phases on each turn: 01, 00, 10, then 11 for a clockwise movement. The FireBeetle on those direction changes would return an odd sequence like 10, 11, 01, 00 with lots of bounces in between. It's almost like the FireBeetle is more sensitive to changes and picks up more "noise" for lack of a better description. I'm using the same encoder and the same dupont connectors for all tests. Outside the direction changes and initial first turn, the FireBeetle catches every other notch perfectly with the correct sequences albeit with a lot more ignored data in between. Here's a dump of the FireBeetle first turn: L: 1 R: 0 LRMEM: 14 LRSUM: -1 RETURN: 0 L: 1 R: 1 LRMEM: 11 LRSUM: 0 RETURN: 0 L: 1 R: 1 LRMEM: 15 LRSUM: 0 RETURN: 0 L: 0 R: 1 LRMEM: 13 LRSUM: 1 RETURN: 0 L: 0 R: 1 LRMEM: 5 LRSUM: 1 RETURN: 0 L: 0 R: 0 LRMEM: 4 LRSUM: 2 RETURN: 0 And this is essentially the same from a Mega2560 sans RETURN field: L: 0 R: 1 LRMEM: 13 LRSUM: 1 L: 0 R: 0 LRMEM: 4 LRSUM: 2 L: 0 R: 0 LRMEM: 0 LRSUM: 2 L: 1 R: 0 LRMEM: 2 LRSUM: 3 L: 1 R: 1 LRMEM: 11 LRSUM: 4 CLOCKWISE 205 The Mega resulted in the counter incrementing clockwise, while the FireBeetle returned no result. On the second turn however it registers: L: 1 R: 0 LRMEM: 2 LRSUM: 3 RETURN: 0 L: 1 R: 0 LRMEM: 10 LRSUM: 3 RETURN: 0 L: 1 R: 1 LRMEM: 11 LRSUM: 4 RETURN: 1 CLOCKWISE 205 Note that it didn't register as many LR sequences and instead retained the last sequence 00 from the previous turn. It's quite peculiar and I feel I'm not doing a good job at describing the conditions.
Hey Ralph, I have a project Im trying to make and need a hand. Im using a ky 040 encoder, attiny85 (microusb breakout version) and im trying to output each rotation as a low signal then go back to high. I havent been able to get any code working. So i need to be able to turn the encoder left and have pin 0 on my attiny85 output a low signal (i think i need to use millis here) then delay for whatever time i need to get a smooth reading then go back to 5v. I need the same true for the right hand side but using pin 1. The input pins would work as pins 3 and 4 from the encoder. I also need a way to turn this output to 3.3v (im using this to emulate button pushes on a bluepill project that has uneditable firmware.) Thanks
You want a pulsed output then? I would detect each rotary encoder pulse (change) via the ISR and from the main loop output a fixed 100mS(?) pulse (high, then low) on the pin you need to. That might affect how many pulses you can sequentially detect, or they may even overlap; another pulse arrives whilst the previous one is still issuing its 100mS pulse. You can test this out by connecting a 5v beeper to the output pin - 100mS is quite long enough to hear the beep. You can run the ATTiny85 on 3v3 (so no voltage conversion required) or use a small 4-channel logic level shifter which I showed in a recent video #239 (th-cam.com/video/SStRG-_1wXc/w-d-xo.html ) - looks like a lot of wires but it's very simple.
I've been happy with my ANENG 8009 for about two years though sometimes I wish I could zero it. Price drove me there after someone pilfered my Fluke. Maybe I just need better probes. BEST HACK ever for a cheap multimeter: glue two short lengths of vinyl tubing to the side of the meter, cut v-notch at the top of each then slit from the notch down the entire length. You will have a pair of great probe storage slots. It dramatically improved my meter storage and movement. Can anyone recommend a command to make a sketch stop and wait for a momentary button push before continuing? I'll research it if I know what command to focus on.
Last question first: Connect a momentary button (NO) to a spare pin INPUT_PULLUP and GND. #define stopBtn 16 (or whatever) pinMode(stopBtn, INPUT_PULLUP); while (digitalRead(stopBtn)); This will wait until the button goes to ground. You might have to add in a small delay _after_ the "while" depending on how quick your loop is, as it will read the button _again_ as you continue to press it to GND. μControllers are quick! Or check that the pin is HIGH before testing for low, using a similar while() statement. Nice hack for keeping probes under control. The first manufacturer to invent cordless probes will be a billionaire, for sure.
Casting it to the phone is of some use to me because sometimes in my work I can't always see the meter in some of my tasks, I do industrial maintenance in an auto factory.
Excellent, Joseph. You're the second person to indicate a real Case Use for a secondary screen. I guess the manufacturers saw a need and filled it. Thanks for sharing.
I like my ANENG here at home. Works perfectly. Have had nothing but Flukes and Tek's at my real shop over the years though. ANENG is great except it is very light weight. Nothing near a Fluke. The light weight keeps sliding all over the bench. Maybe I will glue some metal to it somehow.
To stop sliding, you might want to simply try adding friction. Glue a piece of shelf liner to the unit. I get soft shelf liner at my local dollar store.
Hello, following on from this story, I would like to let you know that I was recently contacted by a user who noticed some strange behaviour: When the direction of rotation changed, the program did not take the first "click" into account. I quickly realised that the problem was that this decoder had the opposite logic and the neutral state was 00 (pull-down) instead of 11 (pull-up). I rewrote the code so that both 00 and 11 were accepted as the neutral state. It really is an easy fix. Instead of assuming the neutral state as 11, lrmem = 0b11, the script reads the initial state of the encoder pins and assigns the result to lrmem. If anyone has encountered a similar problem, you now know why.
Pin interrupts aren't always appropriate. I like timer interrupt much more, and then you do more than one thing in those. One thing i dislike particularly is how the age and physical condition of the switch can affect the amount of time available to your program. What if it's an endless barrage of interrupts because the switch is so worn? I think interrupt pins should be used for IO and ideally not for physical switches, especially not when you care about bounce - if you don't, and just care about first contact, you might as well turn off the interrupt once it fires.
When do you turn the interrupt back on though, Siana? If it is just a fixed time (eg 5mS) then I might as well ignore the pulses for the length of time in the ISR (as per my original code). Tricky!
@@RalphBacon Depends. Let's say you're homing towards an endstop switch, or have something of a kind, then you only need the interrupt enabled while you are moving, so you enable it when you start that move. If you're trying to process human inputs, then you may kick off a timer interrupt upon a pin interrupt and then when that fires, re-enable the pin interrupt, but then that's probably a little more effort than it's worth, so might as well just have the timer interrupt running all the time.
For debugging, you mean? Just put a very short Serial.println in the ISR. For non-debugging you will have to do that in the main code when the volatile variable indicates that the ISR has set the 'flag'.
@@RalphBacon Thanks for quick response, I'm a beginner and following your git hub demo , so far pulse counts and direction coming out perfectly fine, but any where I write Serial.println(l); and Serial.println(r); I just get 1, instead of 1 and 0 values. Actually I'm trying to generate the gray codes to visualize the codes on the scope. I'll be grateful if you can give me code snippet if possible.
Used that on Pin Changed Interrupt and it worked the same as External Interrupt - and I still have external interrupts available for when edge trigger is required...
I recreated this setup and code with a Nano (just deleted IRAM_ATTR in code and changed pin#s). It worked nicely, BUT...it only registers a change (ie. serial prints 200 to 195) when i turn it 2 clicks. Very reliable going up and down, always a serial print of + - 5 when I turn it, but it requires a turn of 2 clicks.
That's because your rotary encoder has a detent (click) _between_ the full cycle, just like my Bourns PEC11R. The code I adapted from Marko Pinteric expects a full cycle before it will increment the rotation. You can try the code in my GitHub for video #230 which works just fine on my Bourns rotary encoder but this sketch doesn't have any switch bounce protection (it expects you to be using my switch bounce circuit). If you try that code and it works (but you get switch bounce) I can show you how to correct for that.
Great video! It makes my encoder work without skipping any readings while rotating. I am currently trying to use a rotary encoder to measure the distance the diameter is rotating and display it on an OLED screen. I had everything working but for some reason it would skip one tick every 4-6 ticks. Your code gets it to register every time but for some reason when I add the OLED screen in the void loop() section of the code it no longer counts or displays anything in the serial monitor (or any updates on the OLED display) Do you have any ideas?
It sounds as if your OLED screen is not returning from a call. Put a Serial.print both immediately before and after the call to your OLED in the main loop to see when everything stops. If and when you discover that, then check the wiring to the OLED. Create a separate sketch just for the OLED to prove you can get that bit working before incorporating the rotary encoder part.
@@RalphBacon thank you! I ended up resolving it - for some reason when it is outside of a condition and just sitting in the void loop() section then it freezes it up - I put it inside the conditional if statement for if the encoder has changed so it then changes the screen there and now it works perfectly. Thank you for your help!
Thank you very much for this series of comments (I wish I would have read them sooner). I was having the same problem using a I2C LCD screen to where the code mixed in with my other code worked perfectly while printing to serial (nothing else) but will miss encoder count like crazy as soon as I added the LCD and print calls unless I turned the encoder REALLY SLOW to the point that I was causing my own delay in the "change". Its been driving me nuts for two days now. I was about to comment to the video to see if Ralph or anyone else had any idea what was going on but decided to read though some of the comments first to see if maybe its been brought up already and sure enough, I came across this string comments and after seeing your 2nd comment about being inside the "if statements", I gave it a try and put all the print calls inside the "if statements" and now it works exactly like I had hoped it would. Again, Thank you!
Hey Ralph. You said you might explain what's going on in this code at some point. I hope you will do that. I don't understand some of these variables. int_8t, etc. And its before the checkRotaryEncoder() function, which doesn't make sense to me, nor does int_8t l and int_8t r. Thanks
Variable types in Arduino speak are pretty vague - after all, what _exactly_ is an "int"? The best way of ensuring you (and others) know what the variable type is, is to use the native C++ definitions for int8_t (an 8-bit integer type) or uint8_t (an unsigned 8-bit integer) and so on. Putting the number of bits in the type ensures you can understand its max value too (int32_t, int64_t and the unsigned equivalents).
@@RalphBacon Thanks Ralph! You went through Marko's code and did some mods. I hope one day you will go through the whole thing and do some more mods to make it understandable for all of us, as this is a VERY valuable chunk of code. Regards!
Hi Ralph. You probably don't want to go over encoders again, but I just tried this library today of the ESP32 : Ai Esp32 Rotary Encoder Not too bad at all. Works extremely well for my application. Anyway. Just thought that I'd share that with you. Cheers! Matt
Indeed it does and I have a unit in my hands for installation in my latest project (after all, if I build these things I might as well actually use them!). Thanks for letting me know!
I got your code and gave it a try. Im getting a good up/down count but its only 1 tick for every 2 detents feedback on the encoder. I looked at the signals on the encoder pins and they do change on each detent so not sure where Why the count is /2. I am also using MCP23017 to read encoder with interrupt for the Arduino to trigger the routine. Thanks for the video you are my goto channel for Arduino modules.
That's because your Rotary Encoder is one of those strange beasts that have a detent at two places during the full step. However, if you detect both rising and falling (so, CHANGE, not RISING or FALLING) edges on one of the pins you will find it works as expected.
I don't have a variable called Counter in my sketch; do you mean this: // A turn counter for the rotary encoder (negative = anti-clockwise) int rotationCounter = 200; The above just sets the start point for the counting. Set that to zero. Then, to only allow zero to 30, change the part of the loop from: // If valid movement, do something if (rotationValue != 0) { rotationCounter += rotationValue * 5; Serial.print(rotationValue < 1 ? "L" : "R"); Serial.println(rotationCounter); } To: // If valid movement, do something if (rotationValue != 0) { if (rotationCounter > 0 && rotationCounter < 30) { rotationCounter += rotationValue; Serial.print(rotationValue < 1 ? "L" : "R"); Serial.println(rotationCounter); } } Experiment a bit. And replace that magic number of 30 with either a #define or const.
Well that was a fun couple of hours - trying to work out why this would not work for me. After wasting 30 minutes chasing a serial driver issue that was actually a broken data line on the USB Cable - It turns out that the 2 spare encoders I have to hand seem to skip the 4th switch state going 11-01-00-11 and 11-00-01-11 rather than 11-01-00-10-11 etc... so the code never worked !
Hmm. Sorry to have caused you such wasted time, Bob. 1. Are you sure you have interrupts on both CLK and DT pins (pinA and pinB), Bob? Put a short serial.print (bad boy, Ralph) in the ISR so you can be sure. 2. Are you also sure your rotary encoders are the 'pulse' type (ie with switches) and go open circuit when in the resting state (brought high only via the 10K resistor to VCC)? 3. Are they actual detent type encoders or do they spin freely? So many questions!
@@RalphBacon Hi Ralph, some years ago I replaced the speed control board on my Electric Golf Trolley with my own design based on an Arduino Pro Micro and I was using my spare board for the test. I 'scoped it out this morning my problems were caused by the board socket being wired A,B,Common and the switches are A,Common,B Aarrghhh! The board has been working in the trolley for years mis-wired ! Thanks for the help. This afternoons job is to try your version of the encoder code with the correct wiring this time. thanks Bob
This is a great bit of code and it works perfectly for me...but. when trying to make the routine into a library. The line attachInterrupt(digitalPinToInterrupt (_pinA), rotary, CHANGE); when placed in the .cpp file produces the error , lib/Encoder/Encoder.cpp:20:64: error: invalid use of non-static member function which seems to be attached to the mode selector CHANGE
If you're creating a library for this, the ISR function you are calling (here 'rotary') must be a function within the class and therefore must be referenced thus: class::function so if your class is called IrvClass your function is IrvClass::rotary The CHANGE is just a numeric value defined by the Arduino wrapper and won't cause an error. Any good? 🤷♂️
It seems that the "volatile bool" that the interrupt routine sets can't be a member of the class. I moved its declaration outside of the class def and at least the code compiles. Now to try and get it working.
@@RalphBacon Here's where my Google foo has led me. You can't call attachinterrupt in a class. I found MANY complicated and convoluted work-arounds that were well beyond my pay grade. Just as I was about to give up and just pray for a solution to fall in my lap, (from you), I discovered that you CAN call attachinterrupt from a class. BUT your ISR and the variables it plays with cannot be class members. I just moved their declarations. I now have a working library that I can hook up to about a dozen h-bridges. Thank you; liked, and subscribed! I'll be seeing more of your channel in the future!
Great stuff, and I'm happy you got it to work. That means that the attachInterrupt call is being done in a "singleton" function; all class instances will use the same function. Good to know.
If it works well enough, I guess there's a limit to the value of continuing to tweak it. But it seems to me that while the algorithm discards any transition sequence that contains an impossible transition, it doesn't make sure that in starting over both switches are open (it's at a detent). Also, using interrupts, even a "no change" result is impossible (there must be a change if there's been an interrupt). And then finally, when you get around to reading the pin values, continuing bouncing may give you different pin values than what actually triggered the interrupt. I don't have an encoder to play with, so I would appreciate it if someone could modify Ralph's code to add the volatile integer variable intCount. It would be incremented every time the ISR runs, and printed out, and then reset, each time the temp value is printed. Basically this would tell you how many interrupts had to be serviced between two detents. For a perfect encoder it should be 4, but I'd like to get an idea of what's actually happening - how much bouncing is going on. A while back I worked on a version that had the ISR disable the interrupt that just triggered the current interrupt, and enable the interrupt on the other pin. That would prevent the bouncing from even triggering interrupts, and reduce overhead for this to almost nothing. But things get complicated when you change direction because the interrupt is enabled on the wrong pin. Oh, and for the 328P, all I/O pins can do pin change interrupts. it's only D2 and D3 that can also do specific edges, and each with its own interrupt vector. For the other pins, each port has one pin change interrupt vector, and you set a mask register to enable specific pins.
I tried the disabling of the interrupt that had just fired some time ago (well, several years ago), Sherman. The problem was knowing when to turn it back on. If it is a specific length of time it was easier just to ignore the extra pulses in the ISR. Regarding pin change interrupts, I felt they were too complicated for beginners; hardware interrupts are easier to understand. However, there are several libraries that handle pin change interrupts and abstract the complexity of pin change interrupts away.
The idea is to enable interrupts on one pin at a time. So assuming pins A and B, when A goes low and triggers an interrupt, within the ISR you disable further interrupts on A, and enable interrupts on B. B changed state some time ago and should be stable. Any bouncing on A produces no interrupts at all. Then when B changes state, you disable B and re-enable A. This works fine until you change direction. The problem there is that the pin with its interrupt enabled isn't the pin that changes state. I've worked out a way to deal with that - by expanding the lookup table to 32 entries, with the extra bit being which pin has its interrupt enabled, and logically it works fine. Some of the extra table entries are +2 and -2 to provide for a change in direction. But I need to do further work on what to do when an imposslble transition occurs, and can't do that until the encoders arrive from Bangood. If I come up with anything useful, I'll send you email. We've exchanged emails before (I'm only Sherman Peabody here). Anyway, my hope was to find a way to get acceptable performance with literally only four interrupts per detent no matter how much bouncing takes place. It's never going to be perfect because switches do sometimes continue to bounce after they should have settled down, or because the rotation speed is just too fast. But if I make any progress, I'll be in touch.
Hi to all! I did downloaded and compiled the sketch and after minor tweks everything is working on a wemos d1 mini. The only line that I could'nt figure out was : Serial.print(rotationValue < 1 ? "L" : "R"); Could someone please translate it to me? Best regards
Well done on getting it running. 👍 That line of code is a ternary operator and means: if the rotationValue is less than 1 print "L" otherwise print "R" on the Serial output. (condition) ? expressionTrue : expressionFalse; Shorthand for an if...else statement. Very useful construct to know and after a while you will use it in preference to the usual, verbose if...then construct.
I suspect the price was so high your are unintentionally blocking the memory! Today, this 6000 count multimeter is about £20-25 from Amazon, probably less from China (allowing for VAT etc), amzn.to/3AT8ZZW
Hello Ralph, I am glad you found time to test my code and that you liked it. I also enjoyed your video.
I just want to explain why I did not use interrupts. First, I am a Raspberry Pi guy and I am not very skillful in interrupts - I just translated the code for Arduino so that those who are more familiar with Arduinos can make an easier transfer. And secondly, I just wanted to show that the logic works and that the state of the encoder can be tracked by only one variable (that's why I use that bizarre number 14) - I did not spend much time optimizing the code further.
The use (or lack) of interrupts was most certainly not a deal breaker here, Marko.
I just loved your logic of having "valid" states, even through it took me weeks to get through your explanation (I was house-moving at the time, that's my excuse).
Thanks for the initial comment that triggered me into delving deeper; many of my viewers learned something from our combined efforts!
Happy New Year 2022!
I went with a somewhat different technique that debounces by sampling at a fixed frequency (using a hardware timer interrupt), and looking for a pattern of N high samples followed by one low sample (to detect a falling edge after a stable high). When turning an encoder by hand, the states change slowly enough (not counting bounces!) that sampling works fine.
To simplify things, I noticed that only one of the pins bounces at a given time. Also, I wanted my program's value to change once per physical detent on the encoder, which corresponds to the rate that one of the inputs is changing. So, look for a falling edge on the "clock" input, and simply sample the "data" (or "direction") input to determine direction (it will be stable at this point, and does not need additional debouncing).
Since I'm turning the encoder by hand, I found that sampling the "clock" pin at 10 kHz (every 100 microseconds) worked well. It's fast enough to keep up with the fastest I can turn the knob, and slow enough to detect and ignore bouncing.
I use a single byte (uint8_t) to maintain the state of the last 8 samples of the "clock" pin. It triggers on 7 high samples followed by one low sample. Here is a snippet of the important parts:
volatile uint8_t encoder_clk = 0xFF;
void debounce_callback(void)
{
encoder_clk = (encoder_clk
Sounds good, Mark. Rolling your own means that you not only understand your own solution but also the problem you were trying to fix!
I wanted to put a couple of Schmitt triggers on a rotary encoder (I don't _think_ I did this in a previous video) but never actually got round to designing the simple circuit and board for it. It was in my pre-PCB days.
Fascinating topic, Ralph. AND, a huge thank you to Marko Pinteric !!
Indeed, I found it all fascinating.
Ralph, please ignore this if you've seen another comment from me, somehow I don't see it here. Aneng is a surprisingly good brand. I bought one AN8008 couple of years ago, just to carry in my backpack. Did not expect much from it at first but it exceed my every expectation, so much that I bought another one after a couple of months. I've dropped them, did every kind of mistake one can do with a DMM and both are still working flawlessly. They both agree with my trusty Fluke 87V in all ranges, I can strongly recommend it. Keep in mind that this is for the AN8008 model, I don't know about the other models.
I got notified of your previous comment (where has it gone?) so I looked at the AN8008 on your recommendation.
In the UK they are £43 (about $58) from Amazon, beyond my budget. However, if ordered directly from Banggood or AliExpress they can be had for $20 _including VAT (sales tax)_ which is only £14.75 - an absolute bargain!
I've added this one to my short list! Thanks for the heads up (and re-commenting, a number of my viewers are complaining that their comments disappear. That's even _after_ YT have notified me of those comments. I think some comment-bot has gone mad).
Great video. I modified your code for the STM32 Bluepill and it works great - best rotary encoder code I have ever used. Changes were minor:
commented out the #include , defined pins as PB6,PB7 and PB8, removed the IRAM_ATTR from the interrupt routine, and removed the digitalPinToInterrupt() from the attach Interrupt routines. Worked perfectly on the first load. Thanks for sharing this.
Great to hear, Edward!
Although I would have kept the digitalPinToInterrupt as it abstracts the physical pin away from the interrupt pin number (assuming you're using Arduino-speak).
But, it worked, nice!
@@RalphBacon In the STM32, any pin can be an interrupt, so you just use the pin numbers. My interrupts look like this: attachInterrupt(PIN_A, rotary, CHANGE);
So PIN_A is a GPIO number, not an interrupt pin number?
@@RalphBacon Yes, - #define PIN_A PB6 - and when I said that all pins can be interrupts, that's true, but not all at the same time. If you use PB6 as an interrupt, you can't use PA6 or PC6 - there are only 16 interrupts - one for each bit position in the GPIO registers.
This also works with polling instead of using interrupts FYI. I'm doing a project with ±16 encoders and some switches on an ESP32 so I don't have enough interrupt pins. I'm using multiple HC165 shift registers to read my input (encoders+switches) states, and I'm polling for changes. The function from Marko Pinteric works like a charm! I've struggled with getting correct values from my encoders for many hours, but this seems to work perfectly :)
Glad you got it working OK! Wow! 16 encoders, a glutton for punishment!
Thank you for bring Marko Pinteric's excellent article to my attention -- had to walk the code several times until the "aha" moment. Now I get it - and why you pass over the "black box" code in your review -- takes a bit to get the workings of lrmem and lrsum
Indeed. When you get it, it seems very straightforward. Like most things in life!
I use FUBAR regularly. In the correct context. But never knew it was an acronym. Love it. Cheers
I hope your boards are never FUBAR or SNAFU, Adam.
I bought my Fluke 77 back in 1985 and I still use it today. Yes, I'm an engineer but it's my personal meter.
I can do better :) I still use my 1977 Fluke 8020A multimeter as a second meter. This thing was/is used heavily and it still works perfectly.
Back in the days of yore, sorry, 1985 they probably built these things to _last_ - unlike today where built in obsolescence and/or servicing is more a important cash cow. What, me, cynical?
I think this proves the fluke the better value when you amortize the cost over the years of ownership. The last time I purchased any. Was a few years ago and they are still in operation. Pro or hobby they are the better value
Fluke is/was? a Philips brand. Don’t know who now sells under the Fluke brand. Philips professional lab equipment was quite good.
In this case you are dealing with a sliding contact instead of a bouncing contact. The lowest resistance path is continually changing in location as well as in resistance value. This noise persists until the slide leaves the metal and the resistors pull up the pin voltages to Vcc. Also, quadrature counters simply count back one when the switch 'bounces' and then up again when the switch re-closes -- there is an exception if both switches are bouncing... which is why cheap encoders perform poorly.
Noise is not so much of an issue; it's the state change that will issue the bounce (or actual change, of course). There are so many ways to do this, I might try a hardware solution next.
It's also not a bad idea to put a small capacitor across the A and B switches. Not only does it serve to denounce a little, the current from charging and discharging the cap helps keep the contacts deoxidized.
@@davidwillmore Low-pass filter followed by Schmidt trigger helps too. The Arduino inputs might have a little hysteresis, but another stage don't hurt performance.
Some years ago, when I retired my company issued Fluke had to be returned. I had the same dilemma as yourself. Rightly or wrongly I eventually settled for a budget end Vichy VC99. Other than occasional replacement of the AA cells its proved reliable and sufficiently robust and accurate for my needs.
Quite so, Brian. Even though you had used a Fluke in your professional life you didn't rush out and buy one when you retired. You downgraded to something that has given you good service. That's what I want!
Hi Ralph. Thanks for sharing. After using a couple of capacitors and resistors and using your previous timing method I recently experimented with code similar to this new code. Previous methods served me well until I hit an issue with a troublesome encoder with an ESP32. I was surprised just how well it worked with both slow and very fast rotations with a very noisy encoder. I've placed most of the code in the interrupt routine, which I realize is not recommended but no issues so far. While interrupt pins are scarce on the Nano, as you say there are plenty on the ESP32 which is what I am mainly using now. I've stopped using the encoder modules, just an encoder and ESP32 built-in pullup resistors.
In part 2 of Jack Ganssle's article you referenced there is an interesting statement, "Squalid taverns are filled with grizzled veterans of the bounce wars recounting their circuits and tales of battles in the analog trenches." That was a reference to hardware debouncing, but it seems to be true for software debouncing too :)
I'm very happy it's working, Garry, even with most of the code in the ISR. At least that way you are not dependant on the loop to detect changes. Yes, Jack Ganssle's paper is very good reading!
Just happened to have some of these that I thought would work well for a project I'm putting together, but all the other code I found either required hardware debouncing or required that the it be rotated so slowly that it was almost unusable. Thank you for putting this together and of course thanks to Marko Pinteric for his brilliant idea. It seems obvious in hindsight, but I could never have thought of this.
Indeed. If you use the same rotary encoder as me (Bourns) I can let you have a PCB for this project (more than one in fact). If you want some, let me know otherwise pretend I was never here 😲
I've had an Aneng AN8009 for a year or so....very happy with it.
Easily as accurate as anything else I own. 😁
A year... OK, that's fine. I wonder if it will last two (or five) years? Should these things be considered disposable?
@@RalphBacon At the price, probably.
Having said that, I've got a couple of cheap meters that belonged to my father and are 10+ years old. Apart from blown fuses, they've never given problems.
They seem to be manufactured down to even less of a price than the Anengs, but have still lasted OK.
I guess *guaranteed* reliability is what costs a lot of the extra cash.
On further thought...why not a good analogue meter?
Probably as accurate as you need, and a wobbling needle can sometimes tell you as much as an oscilloscope. 😆
Thank you for this video - I've just found it after a week or so of investigating options for coding multiple rotary encoders (x5) in an Arduino based button box for sim racing. It will hopefully help me quickly extend a matrix-based wiring using Keypad.h and Joystick.h which interprets CW and CCW rotations as sequential button pulses (A-then-B or B-then-A) - so that I can interpret the rotations and output a single button push through the USB HID interface.
Glad it helped!
Thanks Ralph. Nice solution. You could go the whole hog and put the small amount of processing in the interrupt routine. It would allow the net count to accumulate in the background. I know the main loop could potentially have to deal with a count change greater than +/- 1 but that's not necessarily a bad thing. For those who only have a single interrupt pin, use a 2 input xor gate. I couldn't resist buying these single gate schmitt xors this morning (£3.75 for 50 from RS). Also good as inverters and the schmitt inputs allow for a bit of effective light filtering of noisy switch inputs.
Yes, that's possible (and desirable if the loop does a lot of work when we might miss pulses). Or put the whole thing into a separate task, that way we would never miss anything.
Schmitt trigger chips work very well, I tried them and it was amazing!
As an embedded firmware engineer, I often encounter the strategy you've described for decoding quadrature signals. This involves using a state transition table, where each entry represents a state transition and its associated value indicates the count produced by that transition. Encoders typically follow a mechanical sequence that renders certain state transitions invalid, assuming the encoder is functioning properly. These invalid transitions are often detected due to switch bouncing and they yield no count. Valid counts, on the other hand, will output either +/-1, depending on the direction of rotation.
Upon examining Marco's code, it seems that he generates an index by combining the current and previous states. While functional, I personally find this approach less readable. Therefore, I tend to create a 2D array that can be indexed using the current and previous states. Here's an example written in C:
// Union type representing encoder input states as a byte
typedef union
{
struct {
uint8_t A : 1u;
uint8_t B : 1u;
uint8_t : 6u;
};
uint8_t bState;
}EncoderState_t;
// Transition tables for X4, X2, and X1 resolutions
const int8_t X4_TRANSISTION_TABLE[4][4] = {
{ 0, 1, -1, 0 },
{ -1, 0, 0, 1 },
{ 1, 0, 0, -1 },
{ 0, -1, 1, 0 }
};
const int8_t X2_TRANSISTION_TABLE[4][4] = {
{ 0, 0, -1, 0 },
{ 0, 0, 0, 1 },
{ 1, 0, 0, 0 },
{ 0, -1, 0, 0 }
};
const int8_t X1_TRANSISTION_TABLE[4][4] = {
{ 0, 0, -1, 0 },
{ 0, 0, 0, 1 },
{ 0, 0, 0, 0 },
{ 0, 0, 0, 0 }
};
Here's how the encoder data can be processed in practice:
static void ProcessEncoder(CCL_ENCODER_Encoder_t *pEncoder)
{
// Variable declarations
int8_t chCount = 0;
uint8_t bCurrentState = 0u;
uint8_t bPreState = 0u;
// Update state tracker and get current encoder state
pEncoder->preState = pEncoder->currentState;
pEncoder->currentState.A = pEncoder->pHal->pfnReadPin(pEncoder->pinA);
pEncoder->currentState.B = pEncoder->pHal->pfnReadPin(pEncoder->pinB);
// Translate states to byte representation
bCurrentState = pEncoder->currentState.bState;
bPreState = pEncoder->preState.bState;
// Determine count based on encoder resolution
switch(pEncoder->resolution){
case RESOLUTION_X1:
chCount += X1_TRANSITION_TABLE[bCurrentState][bPreState];
break;
case RESOLUTION_X2:
chCount += X2_TRANSITION_TABLE[bCurrentState][bPreState];
break;
case RESOLUTION_X4:
chCount += X4_TRANSITION_TABLE[bCurrentState][bPreState];
break;
}
// Update position changes
pEncoder->lastPosChange = pEncoder->posChange;
pEncoder->posChange += chCount;
return;
}
Note that the encoder's resolution is dictated by how many edges you detect. For full resolution (X4 mode), every edge of both inputs is detected. However, certain encoders feature mechanical detents but allow you to detect half detents. If you want to count only these detents, you'll need to adjust your implementation, which is why we provide transition tables for each resolution mode (X1, X2, X4).
I noticed in your implemation that you only set a flag in your ISR, please keep in mind that if you don't read the encoder state in your onChange ISR, you might still miss transitions depending on the duration of your main loop and the speed of rotation. Personally, I call ProcessEncoder within the ISR, although this depends on your system's resources.
Despite these precautions, it's possible to register incorrect counts if the encoder spins rapidly enough that the transition time falls below the switch bounce time. In such cases, other mechanisms are necessary or an optical encoder approach may be more suitable for achieving the desired speeds.
Nice. 👍
But missing transitions is not always a Bad Thing. My car radio just ignores my fast spins and forces me to take a more laid back approach. So rather than trying to keep up with some idiot user (in the car case, me) spinning the volume control up or down manically, it just ignores those pulses until I slow down to a reasonable level.
I wouldn't advocate doing any processing to speak of within the ISR. Although if you used an ESP32 you could fire off a task (even on the other core, with great care) to do whatever processing you need triggered from the ISR.
That BT functionality is great for measuring something like signals over long fixed cables
Indeed, a remote meter, so to speak. I didn't think of that, sitting here in my workshop where everything is to hand. Good point, Rob.
Yes, FUBAR! I still remember when my Dad taught me all three (I'm 60+): SNAFU, TARFU & FUBAR. SNAFU: Situation Normal, All Fouled Up. TARFU: Things Are Really Fouled Up. FUBAR: Fouled Up Beyond All Repair (or Reason). Words to get you through any day! And Foo Bar came from the comics of the time. Since the actual use of FUBAR wasn't allowed, they simply replaced it with FOO BAR, which was allowed (it was often written as "FOO" & "BAR" around the edges and such...). And that is where the FOO in FOO FIGHTERS came from. They were those sightings of aircraft that did weird things in flight: They were FOO.
And just to close the loop, so too speak, the Foo Fighters (USA rock band) took their name from those UFO-type foo fighters.
You're a font of knowledge, Philip!
@@RalphBacon Thank you. And my thanks to my Father.
I bought a Owon B35T+ specifically because it had bluetooth for two things - realtime display if you have to have the meter somewhere you can't see it (i.e. remote meter) and because it supports data logging... which I think the ANENG partially supports the concept of - as it has the graph. What I really liked about the B35T+ was that it has so-called "offline" data logging, where it could be set to record X number of measurements, Y times per second, and then you could walk away, and then download the log over bluetooth later to see what happened during the session. However, it's over double the price of the ANENG, and unless you need the datalogging, I think that's probably all the meter you need, and has it's own bells and whistles ;)
And I love Marko's technique to eliminating debounce... I basically see it as a state machine, whereby you lockstep through the states, meaning out of sequence is invalid, and only the next mechanically possible sequences is valid... nice! :) I've always though the wait and other techniques to mitigating debounce were pretty poor... but not as poor as certain manufacturers shipping products to end users without any debounce at all... we have a microwave with a rotary encoder you can't turn fast else it goes backwards :D
Your microwave issue is exactly one of switch bounce and missed pulses. The manufacturer did not test it enough! If they had used Marko's method they (well, you) would not have that "backwards" issue.
Thanks for the info on the multimeters, I've now got a short list so we shall see soon what I end up with.
I should point out that there are dedicated IC for button debouncing. Maxim has MAX6816 & MAX6817 (single & dual switch, fixed 40 ms delay, 2.7V to 5.5V supply). Onsemi has MC14490 (6 switches, programable delay by a single capacitor, 3V to 18V supply). And there could be more. A bit expensive (MC 14490 is 5,56 € on Mouser, MAX6817 is 4,16 €, MAX6816 is 2,69 €), but if you want reliable debouncing with ESD protection (industrial setup?) AND your code has its hands full (already allocated HW interrupts, hugely input dependent timing for the main loop, etc) AND isn't reasonable to dedicate a uC for the buttons, then you may consider a debouncing dedicated circuit. Look, I'm not afraid of writing code, 30 years ago I was writing debouncing code in ASM, but sometimes a better tool for the job is not always in the usual toolbox.
Indeed; I tried out a simple Schmitt trigger circuit (costs pennies, from Jack Ganssle's paper, I believe) and it works very well. The trade off, as you summarise very well, is whether we want additional hardware components or are prepared to do it in software (the accountants will insist on the latter, of course!)
I have the Aneng 8002 multimeter, and it's great. Modern DVMs are great, and very accurate as you said.
How long have you had it, Brian? Do they last?
@@RalphBacon HI Ralph, I've had the Aneng for a couple of years now, not used daily, but quite often. the stand is a bit naff, but used flat it's fine.Of course you will need better leads, but they are standard shrouded 4mm. As you are not looking at the same model, I'll not go into too much detail, suffice to say that I have used some very high end meters in my time ( including the ubiquitous Flukes) but this is probably one of my favourites.
Thanks for the feedback, Brian.
Thank you Ralph!
This code (and a bit of help from another comment string in the comments) got part of a little project I'm working on working great without having to go over to a more expensive (and not really necessary for this project) optical encoder.
Nice work! I'm glad it helped. Optical encoders are, of course, bounce-free but not cash-free!
@@RalphBacon That is very true.
Question for you, any chance you know why it seems many other functions if they are outside of *if statements* seem to cause problems with the encoder and this code?
There was a comment string below where someone was having an issue with printing to an OLED and it turned out that as only as the print calls where in an *if statement*. I had that same problem initially too but with and I2C 16x2 LCD which putting the print calls in *if statements* corrected the issue with the encoder counting and missing counts. The problem is, as I add other code for other aspects that have nothing to do with any printing starts to do the same thing to where the encoder changes get missed by the system.
and I have to apologize a head of time because I'm sure I'm not explaining it as well as it could be and could be using incorrect terminology, I am pretty know to the whole world of microcontrollers and coding for them.
What does this magic "if" statement do?
@@RalphBacon Hi Ralph, thank you for your acknowledgement and inquiry into my question / problem.
Hopefully I can explain it well enough, as mentioned previously, I am new to the whole coding microcontrollers so I might be using wrong terminology.
Anyways, here is two sections of the code in order as I have them in the loop so to possibly help
///////////////////////////////////////////
//Reads Serial Data from Scale and Prints//
///////////////////////////////////////////
if (startState == LOW){
currentWeight();
lcd.setCursor (8,0);
lcd.print (currentScaleWeight); lcd.print(" ");
lcd.setCursor (15,0);
lcd.print ("*");
lcd.setCursor (15,1);
lcd.print ("*");
}
if (startState == HIGH && coarseFine == HIGH ){
lcd.setCursor (8,0);
lcd.print ("FINE ");
}
else if (startState == LOW && coarseFine == LOW){
lcd.setCursor (8,0);
lcd.print ("COARSE");
}
Originally, I had the currentWeight(); function outside of the first *if* statement and that seemed to be causing the encoder to miss counts unless I turned it really slow to where it was a very slow and controlled fall into the next detent of the encoder. I only had the first if statement to chance the display to show the current weight data from the scale instead of the COARSE or FINE so I chalked it up to the reading serial data from the scale (which is a software serial read, not hardware serial if that matters) was causing it because when I put it in the if statement so that it would only read that when the startState was pulled low by the "RUN" button I have on the panel, it seemed to work fine.
I however then realized that I wanted it to change back to showing which adjustment mode I was in when the startState when back high (i.e., stopping the run mode) so I wrote that 2nd *if* and *else if* to handle that and that is when it started acting up again to where it would miss changes from the encoder unless I turned it really really slow.
Could it be that it is staying stuck with the currentWeight(); function running and if so, is there a way to force it to be canceled? I though that when startState when HIGH that it would automatically end that function. Am I wrong here?
Are you using interrupts to detect the rise/fall of the rotary encoder, as per my demo? That is, have you got an ISR function that is called whenever the rotary encoder value on one of the pins changes?
I brought a multi-meter a few month ago from Mustool. I wanted a High current capability as well all the standard measurements. It was a good price and I am very pleased with.
I've certainly heard of Mustool, but they didn't appear in any reviews I looked at. As I don't need high current capability (10A occasionally is more than enough) I'll keep looking, thanks for the info.
@@RalphBacon Just to say Mustool do a whole range of meters.
I'll take a look, I might as well view the entire market whilst I'm at it.
Thanks for this ... it was the solution to an issue I was having. A while ago I put together a test setup to learn about Arduinos (Mega & Nano as well as ESP32) and have a number of breadboards containing various bits of circuitry connected to LCD displays, LEDs, Temp sensors etc as well as one connected to a Servo & a Stepper. Using the Arduino IDE examples as a starting point I created code to read a Pot and use it to set the position of the Servo, and a Rotary Encoder to set the position of the Stepper. It basically worked OK, but had issues with the Servo chattering and the Stepper behaving weirdly. I found that the 5V supply rail was noisy and after fixing that, the Servo was OK ... but the stepper was still acting weirdly.
It seems that it was indeed switch bounce - I modified my code to use the method you describe here and no more issues.
Well done for your perseverance and getting it resolved, Duncan. And now you have so much more experience.
Ralph: I'm in the process of learning how to wire and program and Arduino for a project I'm working on. I've watched a few of your videos, and I must say "Awesome!". Everything is very well explained. The camera shots are excellent. The editing excellent. Thank you so much! I will probably have only one Arduino/Stepper motor project. I'm building a Power Feed for a Bridgeport Milling Machine. But it still requires me to learn all about Arduinos, coding, switches, potentiometers, encoders, etc. So, thanks for your help. Regards.
Glad I can be of help. Of course, if you learn all that for just ONE project that would be a waste. Try building something else too!
@@RalphBacon Thanks Ralph. With the help of you and several other TH-camrs tutorial videos, and, believe it or not, Google's AI Bard, I have gotten a big part of my project working. Just need to add a Rotary Encoder to make steps on my Stepper Motor. Thanks again. Regards.
@@RalphBacon Speaking of components, I bought one of those electronics kits with an Uno, breadboard, switches, and a host of other parts, to see if I could learn this stuff. And it was ok for that. But I would classify most of the parts as not the best quality, and to do some serious, regular work, some better quality parts are needed. Parts that actually FIT in a breadboard, and are not complete crap. Yet, I don't want to spend 10 or 20 times as much for a part, if a decent quality one is available for less. The project I'm working on is an industrial machine, that is now used in a hobby setting. But I want reliable parts that will hold up. I'm not interested in some of these "toy" projects that are often seen in TH-cam Arduino projects. Can you help with recommendations for parts and sellers of them, that fit the above described requirements? Or point me in the right direction? PS: I'm in the US. Thanks
OK, you are correct inasmuch that kits of parts, whilst educational, are not fit for actual project use most of the time.
Reputable suppliers such as DigiKey, Mouser, RS Components, Farnell will supply better quality components - with a guarantee behind them.
That said, most parts are made in the Far East and you, the consumer, must therefore sift the wheat from the chaff. Ignore those that are selling quantity over quality, or aka building DOWN to a price (hobbyist/educational stuff that you've had experience of) rather than those building UP to a particular standard.
Remember that iPhones are mostly built in the Far East but use quality components and have great QA attached to them. They must source their components from somewhere and there are doubtless a few warehouses of "surplus" Apple-designated stock that you can tap into.
It's a minefield but it's good that you are aware; spend some time on the above mentioned suppliers and see what you can find (but take a deep breath when you compare the prices too!).
@@RalphBacon Thanks Ralph, for taking the time. I appreciate it. I do buy most of my electrical parts, such as Relays, Contactors, and Switches, on Amazon and have had no problems with imported parts. But when it comes to electronic parts, I just need to differentiate between the toy parts and the Big Boy parts. Thanks. Regards.
"And you can read his paper for MANY WEEKS!" Great line Ralph! Thanks for the tutorial.
Glad you liked it! Did you read his paper yet, Gord? Understand it? Well, you're a better man than me.
@@RalphBacon No I did not read it. Thanks for "Takin' one for the team" Ralph 😃
You can get optical rotary encoders for free from printers or computer mice (scroll wheel). At least for experiments, as you will usually get the encoder wheel and the sensor as two separate parts and not a single device with pins. But it is still very easy to mount them in a way so that they will work. The printer ones have a very high angular resolution. I remember having used one mounted to a motor shaft for constant speed control using an AVR. It has been a long time ago (so long that Arduino wasn't even a thing ;) ) and don't know what I actually wanted to use it for, but I know that it worked very well (I also implemented a PID loop for more stability). But I am planning to use one soon (more likely from a mouse wheel than a printer), because I do want an MPG (manual pulse generator) or jog wheel for my CNC. Sure I could get a complete MPG for 30€ from ali, but where's the fun in that! ;)
Excellent repurposing idea there! But quite a challenge I would have thought - then again, you've done it before so your experience already tells you what to expect. And you save $30 from Ali!
@@RalphBacon
As it was pre-my-workshop times I remember having built the frame for my test setup with "Fischertechnik" (don't know if that's a thing in the UK) and clamped the motor, encoder wheel and sensor to it. Just to give you an idea how fiddly it is, i. e. not at all. The sensor itself is just an IR LED and two phototransistors. With pullups it is pretty much a drop-in replacement for a mechanical encoder. No software changes needed at all (except maybe that the pulse rate can be much higher becuase of the higher resolution).
I applaud you for your excellent use of the term FOBAR.
Always family-friendly acronyms here, Jim!
@@RalphBacon and, now that I look at it, my complete inability to type.
the sharing to phone function is handy for logging data over a period of time, screenshots and you can put the meter on the circuit you want to measure which can be in another room and read the data of your phone without the need for another person holding the leads on the circuit, it's the ultimate isolation so it's safer for the user, there may be more what makes the Bluetooth connection handy.
Very true, that is a good point. A remote screen (effectively) could be more convenient and safer, especially if logging for an extended period. 👍🏻
Nice vid Ralph. I have done battle with these cheapie RCs and yes they are noisy as old Harry. I am going to have to investigate this new algorithm. I like clever debouncing, Gansells methods for normal switches are really good, just shifting bits into a register running all the time then when you want to check your switch, you just look at the value of the shift, all zeros one state, all ones the other. Any mix and you can safely discard it coz it's bouncing.
On the subject of DMMs, I bought a fluke 10 for 70 quid back in 1992 and it is still my daily driver today. They seem expensive but you are expecting to spend £30 every three years, my fluke hast cost a lot less in the intervening 30 years. Quality wins in the end.
Then again, I thought that buying a Brymen for $100+ was going to be a good investment for at least 5 years, and hoping for 10. Failing after 2 years is unforgiveable. What if a Fluke did that?
@@RalphBacon I agree... And who's to say that fluke haven't "outsourced" production, they could be just as dodgy. What you need is a time machine 😆
Great video , as usual. Your number 1 Aussie Fan.
Hey David, will have to fight you for that title. 😉
Awesome, thank you!
Mike, you need have no worries!
I've had that Aneng meter for a couple of years now and it works great. no problems yet. it does have the short back light time though.
I can live with a short backlight; it stops the battery going flat too soon and I don't often turn it on.
@@RalphBacon Then that would be a good one for you, I love mine. I bought it as a second or third? lol. meter but I use it all the time it's my favorite.
The only thing I found with the cheaper multimeter was some odd mA range choices. With rotary decoding, if you add one quad nand gate chip (74HC00), you can do the decoding using only one interrupt pin as an input and one more pin set as an output (any pin). This can save the second interrupt for some other use. The interrupt is set to change of state and the output pin controls which of the two input lines goes to the interrupt pin through the nand gates. The sequence would be, wait for interrupt, change the state of the output pin, read the interrupt pin state and the output pin state (for the movement) and then leave the int routine. The next interrupt will occur when the other input from the encoder changes, it will ignore any further changes from the original pin.
Wiring: each output from the encoder goes to one input of a nand gate (two gates used). Both nand outputs go to nand gate input on a third gate. Output of the third nand goes to the interrupt pin. Control pin goes directly to one of the encoder nand gate second inputs and is also inverted by a 4th gate (wired as inverter) and goes to the other encoder output nand gate input.
In this way, the output pin selects which encoder output goes to the interrupt. You get an interrupt, you switch to the other encoder output and you read it's state and the current output pin state (both tell you which way the step went, clockwise or anticlockwise). You then wait for the next interrupt and do the same.
Two encoders to be connected to the two interrupt pins independently.
Yes, and not down to μA range either.
Get an old and used Fluke from your local auction site. Excellent value :) Keywords for a cheap optical AB encoder seem to be "Photoelectric Encoder Speed Sensor" marked with TTLH and 4 wires. these are about $4 altough they require the DIY of an axle and a knob to make it into a proper encoder.
Thanks for the tip!
It has been years since I wrote an ISR, but I think the first line of code in the ISR always disables the interrupt. You don’t want your ISR interrupted while it’s servicing the interrupt or you risk overflowing the stack.
Then, the last line before you return should reenable the interrupt.
I love your series of videos. Thanks for doing them.
Hey Steve, thanks for the kind and supportive words. I'm glad you find my videos enjoyable 👍
Regarding the ISR, on the AVR (Arduino-like) modules my understanding is that the ISR won't (cannot) be interrupted (they are _automatically_ disabled upon entry to the ISR) but one further interrupt (or the conditions that make it happen) can get queued. After that they get discarded. So recursive or runaway interrupts don't happen (thank goodness).
On the ESP32, with the Arduino framework, it is a similar situation; but natively (using the ESP-IDF) you can get runaway interrupts (followed by a system crash). 🤦♂️
I have always thought the Rotary Encoder to be a very versatile UI control. It can be used to scroll through a menu (with a display, obviously) and the PUSH_BTN serves to select the new mode. If that mode is an adjustment, one uses it as you have done (or intends to) with temperature control. Another press of the PUSH_BTN 'fixes' the adjustment and returns to the menu. Some selections on the menu may display another menu and so on. I wouldn't suggest this for, say, a word processor but the above demonstrates the versatility of the Rotary Encoder. Now to the intriguing algorithm in the video. I immediately, wondered, whether this could be achieved with a few logic gates. Yes, I know one has a MCU so let that do the work but a hardware solution could be designed as a daughter board that could be used in any project. Just running it up the flag pole to see who salutes it. Great video, Ralph and my condolences on the loss of your multimeter I have an old AVO that still works after over 50 years, if you need it
Funnily enough, Michael, I have a 40 year old analog meter that still works just fine, but not having an exact figure displayed is very disconcerting to me!
Logic gates for the rotary Encoder. Hmm. If I had the time, I'd accept the challenge. But as it happens...
Poss. +'s of broadcasting to phone...
Can take screen shots
Can show someone the readings who isn't able to see the DVM (possible remote)
For video&audio recording/archiving purposes.
Good points - that I had not thought of. Now that you have mentioned them I'm beginning to wish I had a remote phone screen capability on my DMM!
Lots of reasonably priced meters out there - just check the weight of the unit. Nothing more annoying than the thing shifting around as you are trying to get the probes into position and needing to add bluetac to keep it stable. Happy shopping!
Thanks for the tip! Happy shopping? I get sucked down each rabbit hole and can spend hours searching for the Holy Grail of cheap multimeters!
I am definitely going to try this code. One reservation I have is that there is a possibility that there will be time between the interrupt and the processing thereof, because it is done in the main loop. This could potentially cause delays and missing clicks…
Agreed that any time-critical processing done (or detected) in the main loop is going to risk missing an external signal. You could modify the sketch to have interrupt(s) on the rotary encoder pin(s) which would ensure their detection, if not the actual processing (still done by the main loop).
On an ESP32 you could easily write a task to do the processing of the rotary encoder clicks along with an interrupt for the detection thereof; obviously such code would not have been suitable for a demo but I often put stuff into tasks to get them out of the main loop, especially when they are long running (or have delay😲 statements in them).
@@RalphBaconThanks you for replying Ralph. I’m gonna test the sketch as is first, and when it works (especially for some stepless rotary encoders I mistakenly bought) I will try to fumble it into the ISR routine…
And: it works flawlessly with those darn stepless encoders as well! Very, very pleased with that code!
I also managed to incorporate it in the interrupt routine!
Thanks again Ralph for sharing this find with us.
Might you be interested in the ISR incorporated code, let me know.
Glad you got it working Gerard! Regarding the ISR, I did actually get a version working but it was too messy for demo!
The "RotaryEncoder" library by Matthias Hertel uses this technique (and later wrote my own just for fun :) ). I think one pitfall of using interrupts is that you can get many spurious interrupts by the switch bounce. I've used a combination of both hardware (filter) and software (state technique) with interrupts for good measure.
Hardware is certainly the 100% answer. I can't remember if I did a video on this (no, I'm not watching old re-runs) but in my experiments it was fantastic. Just half a Schmitt trigger chip and the problem was gone!
23:30 Optical rotary encoder will do a switch like bounce if there is a voltage drop sufficient to cause the output transistor to flip flop between hi-lo state even when there is no movement. It is an out of voltage spec situation.
Well, OK, if the voltage drops then all bets are off for the rest of the circuit too!
@@RalphBacon Optical rotary encoders tend to operate in 5-24V range. Micro-controllers work down to 3V and continue to process the rotary encoder dropouts (electronics do not hold a proper high and waffle states). I have done spinners and they work fine alone but adding few led buttons is enough to get a variable voltage drop (mouse jitter on windows screen). It also does not help when the Optical rotary encoder use 1.5 - 2m leads (encoders are industrial use on feed rollers for distance measurement device with fix diameter wheel) and hey why hack off the extra - dah!
You should go for Fluke. I have two of them, over 15 years of use on the newest and well over 20 on the original one (Fluke 185) and still working perfectly. Tektronix took over the DVM range for a while so my second one is a TX3 although it's identical apart from the colour of the 'sleeve'. Serial optoisolated output to PC. You get what you pay for. Can I suggest using 1.5V LITHIUM Rechargeables to avoid 'battery black terminal rot'.
Fluke is a name I hear and read about a lot. And for companies that depend on their meters working 100% with excellent repair and recalibration support I totally agree.
For hobbyists, though, a $20 multimeter probably is "enough" and easily replaceable when (not if) it goes wrong. That's my 2c anyway. 😲😉
I like UNI-T meters but recently got an Aneng ST209 current clamp and quite happy with it. So sticking with Aneng I just ordered an AN870 meter.
I hope it's everything you hoped for!
@@RalphBacon I hope so too :-)
@@RalphBacon the AN870 multimeter arrived. Checked it against my voltage reference and pretty much bang on the money. The only thing I'm regretting is that I ordered a green one and not a red one. Hey ho. I guess that I'll get used to it LOL!!
As for the rotary encoder, I 'borrowed' some of your code from your previous vid for my motorcycle ignition simulator on an Arduino NANO. I twiddle to adjust the simulated RPM. Works perfectly so I see no need to change it, but no doubt I will take a peek at this new code and see if there's any reason that I need to use it.
Borrow away, it's what it's there for. If it ain't broke, don't fix it. Unlike most manufacturers who do the opposite.
I have the multimeter you are considering and I love - specially the large readout. It's a winner and I paid AUS$23 for it 2 years ago.
2 years! The magic life of a multimeter! I will take your endorsement into consideration, Peter, thanks for sharing.
I saw many good critics and tests for the Aneng AN9002 on youtube etc. So I bought 2 of them on Aliexpress last year. They work fine and do everything I need. In fact they are much better than you really need for most things when working with yC and digital electronics.
Who really cares if your contoller gets 3.297V or 3.3V and if a resistor has 11.001 kOhm or 11.002 kOhm? In most cases it's not the multimeter but they way the measurement is done, what causes the bigger mistake.
Absolutely, Jeany, I totally agree that "close enough" is "good enough".
Very interesting. Ralph, I have learned so much from you over the years. Thank you and best wishes.
My pleasure! And Merry Xmas to you too, Robert, nice to see you here again! 🎅
Suggest that if you want to buy another or even a diff DVM, make a collection of resistors and measure them on day one. Keep the resistors as a calibration set. Could measure their resistance periodically.
A benchmark set of known values. A great idea. 👍🏻
Great follow-up video
Thanks for sharing your experiences with all of us :-)
You're most welcome, Asger.
Still using my old APPA98 first gen, works a charm. Must be running into 20yrs new.
I bet 20 years ago they built them better than today.
Excellent content as allways Ralph.
Glad you think so! Nice to see you here, Hans.
Interesting video... Why not use a RC low pass and feed it into a schmitt trigger and thence to the uC ? Some uCs have ST input pins, so that can be eliminated as well. SMT components will not increase the PCB size hugely , I reckon. I do this for tact switches.
Yes, I've used SMD Schmitt trigger ICs to achieve this too - but it does complicate the entire circuit to a degree. But it did work 100% without any software intervention at all.
Found this via your amazon review. Great explanation, and seems you have a good channel as well. 👍 subscribed
Welcome aboard! Glad to see you here, Tom.
Quite interesting, thanks for sharing!
I personally now use a timer interrupt for inputs, basically hardware polling, with a little debounce counter that waits until the reading is stable for X samples and sets a flag. Pretty much stole it from AVR freaks and re-wrote it for clarity.
I agree on the Fluke statements, I bought a couple of them, and paying that amount of money and get cheap-ass PVC leads is just plain ridiculous. Never again.
Yes, your interrupt routine is (yet?) another way of doing it. We all have our different ways, I suppose. Flukes are overpriced (like BMWs or Mercs). Cheaper units perform pretty much as well.
@@RalphBacon yep! In the end as long as it work for one self, that's the important part :)
I'm sure going to have a look in depth at the one you described when time permits!
If the meter isn't damaged, I'd maybe consider getting it calibrated but if it does this again in less than two years then I guess that wouldn't have been worth it. I think a meter should last longer than two years though. My UT61E came spot on (tested against a calibrated power supply and I found one value where it disagreed by 0.0001 V) but I haven't tested it recently. Uni-T meters (including the E+) have a relatively high burden voltage and are somewhat vulnerable to ESD applied to the probes according to testing by TH-camr joe smith. The high resolution is nice for being able to see whether a value has settled but otherwise unnecessary for me and it doesn't have a backlight either. I wish it took AA batteries too. PP3 batteries aren't very good value for the energy they store and I prefer rechargeables anyway.
So, reading between the lines, Sean, you reckon your UT61E meter is pretty good but ultimately has more features (or better accuracy) than you needed to pay for? Pretty much what I said in the video 😂
Very interesting and useful. Kudos to Marko, I'll be reading his paper later. Thank you!
I hope it doesn't take you as long as it did me to finally get to the end!
Hey Ralph. Thanks for "De-Coding" Marko's code. I'm going to try your version soon and then see if I can get it modified for my project. I've watched this video several times and glean a little bit each time. Thanks! Regards
So ..... fortunately I had picked up on you demonstrating this in ESP32, as I assumed at first this was Arduino code since you are the "Arduino Guy" and the video thumbnail said "Arduino". It didn't compile and since I'm an Arduino Newbie of only a few weeks, at first I didn't know what the problem was. I did however, figure it out and got it running. (You should be proud of me, Master!) I am however, still trying to make it run my project code if it's turning CW or CCW. (ACW?) instead of just Serial Printing it.
Done well, you have, my young padawan!
Instead of just serial printing it, why not update a (global) variable that has either +1 or -1 (depending on rotation direction) that your main code loop can pick up on and take the appropriate action (as well as resetting the global variable to 0)?
@@RalphBacon Thanks to your help, Master, "Grasshopper" has gotten this code working in my project, and all other aspects of it working as well. Project complete!
I will email a copy of the code and a description of my project, etc. It's pretty cool. might be worth a video! Regards
If you can't justify the cost of a new Fluke meter, they can be great value used. I picked up a Fluke 27 in perfect condition a few years ago on eBay for £25 (plus £6.95 postage). It's accurate, super rugged, waterproof, drop-proof to 3m and it is incredibly stable and reliable. The great thing about Fluke is that they are utterly dependable. Forget about meters failing after 2 years, this will still work perfectly after 20 years.
Totally agree; but I think you were very, very lucky with your find. I don't want to spend $209 on a new multimeter when a $25 will do the job, last 2 years (maybe) and I can then upgrade to a newer, just-as-cheap model in the years to come.
@@RalphBacon I was lucky, but you can still get them and other used Flukes for £60-70, or less if you are prepared to wait. The problem with a $25 meter is that you can't necessarily rely on it even for the first two years. I have a slight test gear acquisition addiction and picked up an Aneng 8008 fairly recently. I used it a lot for a while since it was small and handy, but within a few months I started getting occasional weird readings which became more and more frequent. I think there may be a dry joint or a faulty component on the PCB. I've had similar issues with other cheap meters. A meter you can't trust is worse than no meter at all.
Hi Ralph
About a year ago I had some discussion here about video #19 and interupts/rotary encoders and using one or two interupts on a nano/uno.
Since then I have bought a Nucleo F401RE board. It uses a STM32F401RE MCU I think that's the same as the Black PIlls.
Now while your "old" #19 code worked fine on a nano with the F401RE board I just could not get rid of bounce.
I tried altering the 5mS wait from 1mS up 10mS but I could not get rid of bounce. I assume the much faster MPU must be able to service multiple interupts coming from multiple bounces in under 1mS.
With the F401RE any pin can be an interupt pin so I was eager to try your new #226 code with the F401RE
It objected to the use of a volatile boolean but once I changed it to an normal boolean it worked fine.
In any case with a 32 bit MPU it can access an int in 1 clock cycle so I cannot see any need to use volatile variables.
So I can confirm the code works fine on an F411RE board.
These Nucleo Boards are worth doing an entire video on.
Regards
Dave
I'm glad you got it working on the F411RE board but I must caution you against not using a volatile variable inside the ISR.
The use of "volatile" simply tells the compiler that the value of the variable might change "behind the scenes" (by an interrupt, for example) and that therefore any previously read memory value (for that variable) now stored in a register _cannot_ be relied upon. The compiler should therefore issue code to _always_ retrieve the value from memory and not rely on any register value for that variable.
What sort of compiler objection did you get?
I think the Bluetooth interface back to the DMM could be useful when you're running long tests and don't want to sit in the lab all day while you monitor a voltage, current, temperature etc. Just keep your phone nearby and keep an eye remotely (within the 10m or so that Bluetooth serves).
Good point! I guess you never know how useful something is until you have it available. Like air-con in the car. A larger workshop. Ketchup. The list goes on.
If the phone app is decent, you can probably do logging with it too. I work as an engineering tech, and we have some of the big older Flukes that do data logging, and then you upload it with a USB cable. There are cases when you really need to log data over a long period of time, or watch a circuit to catch when it glitches. Also, just in the thumbnail on the ad page, I noticed that it's showing an analog display. Sometimes that could be really nice as well. Easier to see if something is trending up or down than watching the flashing dots on the meter.
Hi Folks,
Use interrupt on the clk pin, and add a 250nF cap on the clock and dat pins. Also I changed the 10k resistor to 2.2k on the pullups. This Will give you a great starting point. Software will only make it more reliable.
Interesting. So the caps suppress the bouncing by ensuring a continuous signal? And 2k2 instead of 10K to ensure faster charging?
@@RalphBacon The capacitor slows and somewhat smoothes the transitions (it is an analog filter). The downside is that the voltage will be in "no man's land" (between logic high and low) for a while, and that can confuse digital inputs that don't have Schmitt triggers.
The resistor and capacitor values need to be tuned so that the voltage across the capacitor charges/discharges slowly enough during a bounce so that you don't see a change in logic value, but fast enough that it responds to a legitimate change in state. With these cheap encoders, that can be tough since they can bounce badly, but you still need to detect pulses at tens of times per second (when spinning the knob quickly).
I found that the falling edge is quite sharp. The caps keep the noise below the active high threshold of the digital io. I did some tuning on my scope and found that even the fastest spin of the knob had falling edges about 1ms apart. The only software filtering i had to do was to reject any falling edges less than 1000us apart.
Thank you. Very good work!
Glad you liked it!
The rotary switch on my UniT Clamp Meter started giving trouble after around 5 years. Apart from being left in the garage which I guess could be damp it was never mistreated. I was able to take it apart and fix the issue though so it's still going strong.
Good that you fixed it, Ali. I guess 5 years is still not long enough for any kind of meter, I would expect (well, hope for) 10 years.
Hi Ralph ! as a beginner, on a nano, i uncommented void IRAM_ATTR rotary() but can u explain why for line 78: attachInterrupt(digitalPinToInterrupt(PIN_A), rotary, CHANGE); 'rotary' not declared in the scope ? thanks a lot
For the Arduino, remove the IRAM_ATTR but keep everything else for the function.
Im also a beginner and i have the same problem "78: attachInterrupt(digitalPinToInterrupt(PIN_A), rotary, CHANGE); 'rotary' not declared in the scope" cant figure it out :(
It means that it is expecting a function declared called 'rotary' that will be called whenever there is an interrupt. I guess you have either mis-spelled it or have not written an ISR called 'rotary'?
As far as meters go, unless you need extreme precision, my policy is to go with the cheapest one that gets the job done. As long as it meets your accuracy requirements, there's really no reason to but a $50 meter where where a cheap $10 meter will do the same job (prices relative). With the current state of technology, most meters you buy today, regardless of the price, will give you pretty good accuracy for most purposes.
I'm with you, Jason - most of the way. I don't think a $10 meter will cut the mustard, but a $25 meter certainly seems to offer a lot. There's a question about _mechanical_ reliability (that centre knob gets turn an awful lot) but we shall see!
I made a volume knob for my pc with an arduino and rotary encoder(a couple years ago). I don't use it because it makes too big of a change in volume per click. I'm gonna have to revisit it now and see if I can make it have finer adjustment. Maybe this code will help. Thanks!
Or use one of these: th-cam.com/video/XhNiR7XvUVo/w-d-xo.html
Or do what I did using a rotary encoder:th-cam.com/video/_7aN8wivSA0/w-d-xo.html
Unfortunately, Marko's code (the one without interrupts, that is) will trip with delays when the value returned is 0. Normally you wouldn't do anything if the encoder read 0, so no problem... except if you want to blink a value being edited by the user. Which is how I discovered this little problem. My code reads the encoder, updates de value if != 0, BUT, 0 or no 0, it will still do other stuff, namely, showing/hiding the number being edited on a 7-segment display every 1/2 second. As far I checked, a delay(5) will break the encoder state, but only if it returned 0, if it returned 1/-1, then the delay doesn't affect it.
Yes, this has been mentioned before, San; even his original code would / could "miss" a transition if there was a delay in the loop.
My choice of using an interrupt for detection has not improved things without doing more work in the ISR (which is not ideal).
For an ESP32 I would put the entire thing into a task which could track the rotational count for the main loop to pick up on. The downside there is that the rotation could "jump" from 10 to 12, for example, if the loop was busy or had delays.
Ideally the task would handle all aspects of the rotary encoder including inc/decrementing whatever it is that it is being used for.
Always a challenge!
@@RalphBacon Yep, although in this case, strangely, the encoder looses track of the situation and never recovers. I haven't got the time to investigate why (the internal state no doubt), but when it misses a step, it misses them all afterwards! :)
Arduinos have "pin change" interrupts for all port pins, but your service must identify the pin that changed. You can mask off the pins that are not connected to encoder(s) so that their changes do not cause interrupts. This allows up to four counters in 8b ports. I can email you some code if you like.
I've used pin change interrupts, Byron; but I'm convinced I would just totally confuse beginners unless I used a library to abstract the complexity away. I think hardware interrupts are much easier to configure and understand, initially, at least.
It's can be worth looking around for deals on Fluke gear. I picked up a 115 with a soft carry case and a set of accessory probes & leads for £115 in 2016.
The problem is, Michael, that I don't want to spend that sort of money in this climate of rising gas prices and meters that go foo bar in 24 months! No matter how good a deal it might be.
very nice, been waiting for something like this.. great code !
Glad you like it, Patrick, but it was Marko Pinteric that wrote the main routine.
@@RalphBacon I know, but still thank you for bringing it to our attention
its so much better than what I did to counteract the bounces
for all ppl using arduino and getting the error "Compilation error: expected initializer befor 'rotary'"
in this line "void IRAM_ATTR rotary()"
just delete IRAM_ATTR, this is for ESP only.
just use "void rotary()"
also don't forget to change the pins to 2 and 3
Yes, good points for all Arduino UNO/Nano users, thanks for sharing 👍
It seems your thumb is healed up well. Don't argue with a knife. (Ralph "You so doll that you can't cut butter". Knife "I show you doll". SPLAT. LoL
Great work on the encoder code by you and Marco.
3 years back, i got myself a UNI-T 61E, it is far better than i need right now -but what do you need in years ahead?
My 2nd multimeter, actually my first, is a 30 years old Matex 3½ digit. It does all the grunt work.
You have a very good point that most people need to learn -what do I really need and what can i afford, do I need that bragging right.
You could ask on the "LearnElectronic" channel, he has a few multimeters and seems to know the good and the bad.
I'll check out that channel you mention, thanks Flemming. And yes, my thumb is healing, last (nurse) dressing yesterday, now I just have to do it myself. And no more playing with craft knives (dull or otherwise).
@@RalphBacon Get the UNI-T UT15B PRO. It's 53$ right now on AE.
working on a bench, the BlueTooth isn't that handy but imagine you are testing a receptacle in the room adjacent to your fusebox. You can hook up your meter and use your phone to remotely observe readings while you connect and disconnect the circuit. No idea what the BlueTooth range is but it could be handy.
A great use case, Jeff, I knew there must be a reason for a "secondary" display.
Two years is far too short for a meter's lifespan. I've got a digital meter bought at the turn of the millennium (edit: for ~$150 from Radio Shack) that still works perfectly. I have 2 analog meters I inherited from my grandfather which are older than I am. It might be worth reaching out to Brymen and seeing what they say about your meter, presuming you haven't already. If all it needs is a calibration, it might be worth doing (and then you'll have a calibrated instrument!). Dave Jones of the EEVBlog (who sells his own Brymen models) has done a number of shoot-outs in the multimeter space. Aneng is one of the cheap ones that (pleasantly) surprised him, though I think it was a model 8008 he tested.
Edit 2: That's a neat way to handle debouncing. Great Scott takes this a step further. If you're using "change" interrupts, you only really need one interrupt pin. Anytime that pin changes, read both pins. Use that to figure out how far the encoder has moved, in which direction. Also, now you still have an interrupt pin available for something else. 👍
Good points, Ted. I, too, have a couple of multimeters (including a 40-year old analog one) that still work perfectly. But I'm assuming that new stuff is not built to last otherwise how would they keep selling this stuff?
I'll have a look at Great Scott's work too. It seems everybody and his dog (well, not mine, he's more a MicroPython dog) seems to have invented a new way of doing this!
@@RalphBacon Could well be; engineered obsolescence is certainly a thing. OTOH, multimeter tech is pretty mature at this point. I dunno. Watching the results of the chip shortages, ewaste issues, and various logistics nightmares, I'm hoping the pendulum swings back toward repair a bit.
LOL @ uPython dog. 🤣 I think mine must be an Assembler, given her brute force.
That is a great piece of code. Thank you for sharing.
I adjusted the code a bit for an arduino uno and extended it to increment more with a faster turn.
// Has rotary encoder moved?
if (rotaryEncoder)
{
// Get the movement (if valid)
int8_t rotationValue = checkRotaryEncoder();
// If valid movement, do something
if (rotationValue != 0)
{
int interval= millis()-lastMillis; //interval = time between 2 correct cycles in milliseconds.
lastMillis = millis(); //set lastMillis to current time
if (interval > quickTurn){ //int quickTurn is set tot 90ms
//value + 1
incrementSize = 1; //int incrementSize
}
else if(interval> superTurn){ //int superTurn is set tot 40ms
// + 5
incrementSize = 5;
}
else{ //if another turn happens in less than 40ms after the previous one.
//+10
incrementSize = 10;
}
rotationCounter += rotationValue * incrementSize ;
if (rotationCounter < 0){
rotationCounter = 0;
}
else if(rotationCounter > 100){
rotationCounter = 100;
}
// Serial.print(rotationValue < 1 ? "L" : "R"); // I had no nead for the serial.
// Serial.println(rotationCounter);
showValue(rotationCounter);
}
}
Good work! The rapid increment for a fast turn is a good improvement for sure 👍
Hi Ralph, thanks for the excellent work.
Just wondering about the pins you have assigned (32, 4 and 16), none of which are interrupts. I thought you mentioned pins 2 and 3 in the video.
Also wondering about the line that mentions the push button doesn't have a pullup resistor. I do have 3 resistors on the KY-040.
I'm guessing, Jacques, that if I'm using pin 32 then it's not an Arduino (ATMega328p) chip, but is probably an ESP32 device.
On the ESP32, _any_ pin can be an interrupt pin. Unlike the Arduino Uno (or Nano) which only allows pins 2 & 3 as hardware interrupt pins.
Has that clarified things?
@@RalphBacon Thanks, Ralph. Yes, this makes things clear, except for the pullup resistors. The resistors are on the KY-040, therefore I don't understand why any device, ESP or Arduino, would need to connect internal pullup resistors to the pins. I noticed the pullup resistors in the original code by Marko Pinteric but couldn't explain why he has added them to the code. Would you know why he did that?
Yes, I do. 😁 You want more?
Although my HW-040 (aka KY-040) modules do have pullups on the underside of the board, a bare Rotary Encoder will not. Using the built-in pullups of an Arduino etc will ensure that the code works.
If your board has pull-ups then you don't need to make the GPIO a pullup. Otherwise you do. It would probably work with both, actually, as we're bringing the pin A or B LOW so it should be OK even if there is a 10K pullup on the board and an internal 47K pull up (total 8k2Ω).
@@RalphBacon Thanks again, Ralph. I'll give it a try on the Arduino Mega, which has 6 pins that can be assigned to an interrupt (2,3,18,19,20 and 21), although in real life it's closer to 4 pins since pin 20 and 21 are often occupied by components that need SCL or SDA. I have not used interrupt procedures before and I hope that connecting the KY-040 to pins 3, 18 and 19 on the Mega will not cause issues with interrupt signals coming from pins 20 and 21, to which I have already attached a pressure sensor.
Update: After deleting IRAM_ATTR from your code and adjusting the speed to 9600 baud everything works fine, until I try adding an OLED screen. For some reason the OLED screen slows down everything else. I can't get my head around it. The OLED screen print command is within the interrupt routine, simultaneous with the serial print command. What is Arduino waiting for before or after the oled print command?
Hi Ralph,
I have dowloaded and compiled the sketch but get the following error message
"Compilation error: expected initializer before 'rotary'"
Errot points to line " void IRAM_ATTR rotary() "
I am using a Arduino Nano and the IDE V2.1.0
Are you able to help?
Rob
Remove the IRAM_ATTR word, that only applies to an ESP32, an Arduino has no IRAM.
Thanks, Ralph. I just gave the code a spin and noticed that the first "notch" of each direction change is dropped. So the first turn when you start the code, then every subsequent direction change after that. In fact, you can toggle back and forth one notch left then one notch right and get no reading at all. I haven't dug into the code yet to try to code around that but was wondering instead if this was your experience too?
Hmm. That doesn't happen to me, not at all. I would suggest putting in a few Serial.print lines to make sure the change is being noticed. You can even put one in the ISR, you have my permission!
@@RalphBacon Hi, Ralph. I'm still doing my testing but I have narrowed things down to the microcontroller rather than the rotary encoders I was using. Tests on a genuine UNO and third party Mega2560 worked flawlessly per your video.
The FireBeetle 2 ESP32-E that I've been trialing is where my issue lays. I thought at first it may have been because I was using interrupts for the WiFi connection however your unedited sketch saw the exact same result of skipping the initial turn, and subsequent direction changes.
I post my findings once I get to the bottom of it in case anyone else comes across this issue themselves.
Cheers for all you do.
Quick note: I used an ESP32-based board in my demo, no issues. I will look at the FireBeetle to see if it's any different. What pins are you using for the two interrupts?
@@RalphBacon Note that there's two verions of the FireBeetle; I'm using the newer model -E. I used pins 34 and 35. Something I observed is that the Arduinos correctly register all four phases on each turn: 01, 00, 10, then 11 for a clockwise movement. The FireBeetle on those direction changes would return an odd sequence like 10, 11, 01, 00 with lots of bounces in between. It's almost like the FireBeetle is more sensitive to changes and picks up more "noise" for lack of a better description. I'm using the same encoder and the same dupont connectors for all tests. Outside the direction changes and initial first turn, the FireBeetle catches every other notch perfectly with the correct sequences albeit with a lot more ignored data in between. Here's a dump of the FireBeetle first turn:
L: 1
R: 0
LRMEM: 14
LRSUM: -1
RETURN: 0
L: 1
R: 1
LRMEM: 11
LRSUM: 0
RETURN: 0
L: 1
R: 1
LRMEM: 15
LRSUM: 0
RETURN: 0
L: 0
R: 1
LRMEM: 13
LRSUM: 1
RETURN: 0
L: 0
R: 1
LRMEM: 5
LRSUM: 1
RETURN: 0
L: 0
R: 0
LRMEM: 4
LRSUM: 2
RETURN: 0
And this is essentially the same from a Mega2560 sans RETURN field:
L: 0
R: 1
LRMEM: 13
LRSUM: 1
L: 0
R: 0
LRMEM: 4
LRSUM: 2
L: 0
R: 0
LRMEM: 0
LRSUM: 2
L: 1
R: 0
LRMEM: 2
LRSUM: 3
L: 1
R: 1
LRMEM: 11
LRSUM: 4
CLOCKWISE 205
The Mega resulted in the counter incrementing clockwise, while the FireBeetle returned no result. On the second turn however it registers:
L: 1
R: 0
LRMEM: 2
LRSUM: 3
RETURN: 0
L: 1
R: 0
LRMEM: 10
LRSUM: 3
RETURN: 0
L: 1
R: 1
LRMEM: 11
LRSUM: 4
RETURN: 1
CLOCKWISE 205
Note that it didn't register as many LR sequences and instead retained the last sequence 00 from the previous turn.
It's quite peculiar and I feel I'm not doing a good job at describing the conditions.
Hey Ralph, I have a project Im trying to make and need a hand. Im using a ky 040 encoder, attiny85 (microusb breakout version) and im trying to output each rotation as a low signal then go back to high. I havent been able to get any code working. So i need to be able to turn the encoder left and have pin 0 on my attiny85 output a low signal (i think i need to use millis here) then delay for whatever time i need to get a smooth reading then go back to 5v. I need the same true for the right hand side but using pin 1. The input pins would work as pins 3 and 4 from the encoder. I also need a way to turn this output to 3.3v (im using this to emulate button pushes on a bluepill project that has uneditable firmware.) Thanks
You want a pulsed output then? I would detect each rotary encoder pulse (change) via the ISR and from the main loop output a fixed 100mS(?) pulse (high, then low) on the pin you need to. That might affect how many pulses you can sequentially detect, or they may even overlap; another pulse arrives whilst the previous one is still issuing its 100mS pulse. You can test this out by connecting a 5v beeper to the output pin - 100mS is quite long enough to hear the beep.
You can run the ATTiny85 on 3v3 (so no voltage conversion required) or use a small 4-channel logic level shifter which I showed in a recent video #239 (th-cam.com/video/SStRG-_1wXc/w-d-xo.html ) - looks like a lot of wires but it's very simple.
I've been happy with my ANENG 8009 for about two years though sometimes I wish I could zero it. Price drove me there after someone pilfered my Fluke. Maybe I just need better probes.
BEST HACK ever for a cheap multimeter: glue two short lengths of vinyl tubing to the side of the meter, cut v-notch at the top of each then slit from the notch down the entire length. You will have a pair of great probe storage slots. It dramatically improved my meter storage and movement.
Can anyone recommend a command to make a sketch stop and wait for a momentary button push before continuing? I'll research it if I know what command to focus on.
Last question first:
Connect a momentary button (NO) to a spare pin INPUT_PULLUP and GND.
#define stopBtn 16 (or whatever)
pinMode(stopBtn, INPUT_PULLUP);
while (digitalRead(stopBtn));
This will wait until the button goes to ground. You might have to add in a small delay _after_ the "while" depending on how quick your loop is, as it will read the button _again_ as you continue to press it to GND. μControllers are quick! Or check that the pin is HIGH before testing for low, using a similar while() statement.
Nice hack for keeping probes under control. The first manufacturer to invent cordless probes will be a billionaire, for sure.
@@RalphBacon Thank you very much.
Casting it to the phone is of some use to me because sometimes in my work I can't always see the meter in some of my tasks, I do industrial maintenance in an auto factory.
Excellent, Joseph. You're the second person to indicate a real Case Use for a secondary screen. I guess the manufacturers saw a need and filled it. Thanks for sharing.
I like my ANENG here at home. Works perfectly. Have had nothing but Flukes and Tek's at my real shop over the years though. ANENG is great except it is very light weight. Nothing near a Fluke. The light weight keeps sliding all over the bench. Maybe I will glue some metal to it somehow.
To stop sliding, you might want to simply try adding friction. Glue a piece of shelf liner to the unit. I get soft shelf liner at my local dollar store.
Good to know! I hear that Anengs are a bit lightweight in more senses than just the one!
Hello, following on from this story, I would like to let you know that I was recently contacted by a user who noticed some strange behaviour: When the direction of rotation changed, the program did not take the first "click" into account. I quickly realised that the problem was that this decoder had the opposite logic and the neutral state was 00 (pull-down) instead of 11 (pull-up). I rewrote the code so that both 00 and 11 were accepted as the neutral state. It really is an easy fix. Instead of assuming the neutral state as 11, lrmem = 0b11, the script reads the initial state of the encoder pins and assigns the result to lrmem. If anyone has encountered a similar problem, you now know why.
How interesting. Thanks for sharing and Merry Xmas!
@@RalphBacon Thanks. Merry Christmas to you too!
Pin interrupts aren't always appropriate. I like timer interrupt much more, and then you do more than one thing in those. One thing i dislike particularly is how the age and physical condition of the switch can affect the amount of time available to your program. What if it's an endless barrage of interrupts because the switch is so worn? I think interrupt pins should be used for IO and ideally not for physical switches, especially not when you care about bounce - if you don't, and just care about first contact, you might as well turn off the interrupt once it fires.
When do you turn the interrupt back on though, Siana? If it is just a fixed time (eg 5mS) then I might as well ignore the pulses for the length of time in the ISR (as per my original code). Tricky!
@@RalphBacon Depends. Let's say you're homing towards an endstop switch, or have something of a kind, then you only need the interrupt enabled while you are moving, so you enable it when you start that move. If you're trying to process human inputs, then you may kick off a timer interrupt upon a pin interrupt and then when that fires, re-enable the pin interrupt, but then that's probably a little more effort than it's worth, so might as well just have the timer interrupt running all the time.
Hello Ralph, How can I print the PIN_A and PIN_B status (i.e. HIGH or LOW) when the condition is true ? Thanks in advance.
For debugging, you mean? Just put a very short Serial.println in the ISR. For non-debugging you will have to do that in the main code when the volatile variable indicates that the ISR has set the 'flag'.
@@RalphBacon Thanks for quick response, I'm a beginner and following your git hub demo , so far pulse counts and direction coming out perfectly fine, but any where I write Serial.println(l); and Serial.println(r); I just get 1, instead of 1 and 0 values.
Actually I'm trying to generate the gray codes to visualize the codes on the scope.
I'll be grateful if you can give me code snippet if possible.
Sorry my bad I got it now , thank you for your help. really appreciate it. please keep up the good work.
Glad you got it sorted, always better when you figure it out yourself 😉
Used that on Pin Changed Interrupt and it worked the same as External Interrupt - and I still have external interrupts available for when edge trigger is required...
Excellent! I shall have to try this myself. Was that on an Arduino Uno chip?
@@RalphBacon That was on the Nano
I recreated this setup and code with a Nano (just deleted IRAM_ATTR in code and changed pin#s). It worked nicely, BUT...it only registers a change (ie. serial prints 200 to 195) when i turn it 2 clicks. Very reliable going up and down, always a serial print of + - 5 when I turn it, but it requires a turn of 2 clicks.
That's because your rotary encoder has a detent (click) _between_ the full cycle, just like my Bourns PEC11R.
The code I adapted from Marko Pinteric expects a full cycle before it will increment the rotation. You can try the code in my GitHub for video #230 which works just fine on my Bourns rotary encoder but this sketch doesn't have any switch bounce protection (it expects you to be using my switch bounce circuit).
If you try that code and it works (but you get switch bounce) I can show you how to correct for that.
Great video! It makes my encoder work without skipping any readings while rotating. I am currently trying to use a rotary encoder to measure the distance the diameter is rotating and display it on an OLED screen. I had everything working but for some reason it would skip one tick every 4-6 ticks. Your code gets it to register every time but for some reason when I add the OLED screen in the void loop() section of the code it no longer counts or displays anything in the serial monitor (or any updates on the OLED display) Do you have any ideas?
It sounds as if your OLED screen is not returning from a call. Put a Serial.print both immediately before and after the call to your OLED in the main loop to see when everything stops.
If and when you discover that, then check the wiring to the OLED. Create a separate sketch just for the OLED to prove you can get that bit working before incorporating the rotary encoder part.
@@RalphBacon thank you! I ended up resolving it - for some reason when it is outside of a condition and just sitting in the void loop() section then it freezes it up - I put it inside the conditional if statement for if the encoder has changed so it then changes the screen there and now it works perfectly. Thank you for your help!
Very happy you got it working 😁
Thank you very much for this series of comments (I wish I would have read them sooner). I was having the same problem using a I2C LCD screen to where the code mixed in with my other code worked perfectly while printing to serial (nothing else) but will miss encoder count like crazy as soon as I added the LCD and print calls unless I turned the encoder REALLY SLOW to the point that I was causing my own delay in the "change". Its been driving me nuts for two days now.
I was about to comment to the video to see if Ralph or anyone else had any idea what was going on but decided to read though some of the comments first to see if maybe its been brought up already and sure enough, I came across this string comments and after seeing your 2nd comment about being inside the "if statements", I gave it a try and put all the print calls inside the "if statements" and now it works exactly like I had hoped it would.
Again, Thank you!
Hey Ralph. You said you might explain what's going on in this code at some point. I hope you will do that. I don't understand some of these variables. int_8t, etc. And its before the checkRotaryEncoder() function, which doesn't make sense to me, nor does int_8t l and int_8t r. Thanks
Variable types in Arduino speak are pretty vague - after all, what _exactly_ is an "int"?
The best way of ensuring you (and others) know what the variable type is, is to use the native C++ definitions for int8_t (an 8-bit integer type) or uint8_t (an unsigned 8-bit integer) and so on. Putting the number of bits in the type ensures you can understand its max value too (int32_t, int64_t and the unsigned equivalents).
@@RalphBacon Thanks Ralph! You went through Marko's code and did some mods. I hope one day you will go through the whole thing and do some more mods to make it understandable for all of us, as this is a VERY valuable chunk of code. Regards!
@@KW-ei3pi Copy-paste your code snippets into ChatGPT / Gemini / etc ;) I find them quite useful for explaining code that I don't understand.
Hi Ralph. You probably don't want to go over encoders again, but I just tried this library today of the ESP32 : Ai Esp32 Rotary Encoder
Not too bad at all. Works extremely well for my application.
Anyway. Just thought that I'd share that with you.
Cheers!
Matt
Indeed it does and I have a unit in my hands for installation in my latest project (after all, if I build these things I might as well actually use them!). Thanks for letting me know!
I got your code and gave it a try. Im getting a good up/down count but its only 1 tick for every 2 detents feedback on the encoder. I looked at the signals on the encoder pins and they do change on each detent so not sure where Why the count is /2. I am also using MCP23017 to read encoder with interrupt for the Arduino to trigger the routine. Thanks for the video you are my goto channel for Arduino modules.
That's because your Rotary Encoder is one of those strange beasts that have a detent at two places during the full step. However, if you detect both rising and falling (so, CHANGE, not RISING or FALLING) edges on one of the pins you will find it works as expected.
it works ! :) i try to make Counter counts only from 0 to let's say 30. and if i write unsigned int Counter = 0; it may return -255. any idea ?
I don't have a variable called Counter in my sketch; do you mean this:
// A turn counter for the rotary encoder (negative = anti-clockwise)
int rotationCounter = 200;
The above just sets the start point for the counting. Set that to zero.
Then, to only allow zero to 30, change the part of the loop from:
// If valid movement, do something
if (rotationValue != 0)
{
rotationCounter += rotationValue * 5;
Serial.print(rotationValue < 1 ? "L" : "R");
Serial.println(rotationCounter);
}
To:
// If valid movement, do something
if (rotationValue != 0)
{
if (rotationCounter > 0 && rotationCounter < 30)
{
rotationCounter += rotationValue;
Serial.print(rotationValue < 1 ? "L" : "R");
Serial.println(rotationCounter);
}
}
Experiment a bit. And replace that magic number of 30 with either a #define or const.
@@RalphBacon i found an interesting other way:
// If valid movement, do something
if (rotationValue != 0)
{
rotationCounter = min(30, max(1, rotationCounter));
rotationCounter += rotationValue * 1;
Serial.print(rotationValue < 1 ? "L" : "R");
Serial.println(rotationCounter);
}
Yes, a good way of restricting the values.
Well that was a fun couple of hours - trying to work out why this would not work for me. After wasting 30 minutes chasing a serial driver issue that was actually a broken data line on the USB Cable - It turns out that the 2 spare encoders I have to hand seem to skip the 4th switch state going 11-01-00-11 and 11-00-01-11 rather than 11-01-00-10-11 etc... so the code never worked !
Hmm. Sorry to have caused you such wasted time, Bob.
1. Are you sure you have interrupts on both CLK and DT pins (pinA and pinB), Bob? Put a short serial.print (bad boy, Ralph) in the ISR so you can be sure.
2. Are you also sure your rotary encoders are the 'pulse' type (ie with switches) and go open circuit when in the resting state (brought high only via the 10K resistor to VCC)?
3. Are they actual detent type encoders or do they spin freely? So many questions!
@@RalphBacon Hi Ralph, some years ago I replaced the speed control board on my Electric Golf Trolley with my own design based on an Arduino Pro Micro and I was using my spare board for the test. I 'scoped it out this morning my problems were caused by the board socket being wired A,B,Common and the switches are A,Common,B Aarrghhh! The board has been working in the trolley for years mis-wired ! Thanks for the help. This afternoons job is to try your version of the encoder code with the correct wiring this time. thanks Bob
I'm wondering how it worked all those years? Perhaps you had just two speeds, fast and faster? 😅
Are you not still relying on the main loop to be fast enough to see all the state changes of your volatile boolean?
Yes, but the main loop runs pretty fast (unless you're doing lots of other stuff). In which case you might want to use interrupts and/or tasks.
This is a great bit of code and it works perfectly for me...but. when trying to make the routine into a library. The line
attachInterrupt(digitalPinToInterrupt (_pinA), rotary, CHANGE);
when placed in the .cpp file produces the error ,
lib/Encoder/Encoder.cpp:20:64: error: invalid use of non-static member function
which seems to be attached to the mode selector CHANGE
If you're creating a library for this, the ISR function you are calling (here 'rotary') must be a function within the class and therefore must be referenced thus:
class::function
so if your class is called IrvClass your function is
IrvClass::rotary
The CHANGE is just a numeric value defined by the Arduino wrapper and won't cause an error.
Any good? 🤷♂️
@@RalphBacon That was the first thing I tried. No boom boom. Same error
It seems that the "volatile bool" that the interrupt routine sets can't be a member of the class. I moved its declaration outside of the class def and at least the code compiles.
Now to try and get it working.
@@RalphBacon Here's where my Google foo has led me.
You can't call attachinterrupt in a class. I found MANY complicated and convoluted work-arounds that were well beyond my pay grade. Just as I was about to give up and just pray for a solution to fall in my lap, (from you), I discovered that you CAN call attachinterrupt from a class. BUT your ISR and the variables it plays with cannot be class members. I just moved their declarations. I now have a working library that I can hook up to about a dozen h-bridges.
Thank you; liked, and subscribed! I'll be seeing more of your channel in the future!
Great stuff, and I'm happy you got it to work. That means that the attachInterrupt call is being done in a "singleton" function; all class instances will use the same function. Good to know.
If it works well enough, I guess there's a limit to the value of continuing to tweak it. But it seems to me that while the algorithm discards any transition sequence that contains an impossible transition, it doesn't make sure that in starting over both switches are open (it's at a detent). Also, using interrupts, even a "no change" result is impossible (there must be a change if there's been an interrupt). And then finally, when you get around to reading the pin values, continuing bouncing may give you different pin values than what actually triggered the interrupt.
I don't have an encoder to play with, so I would appreciate it if someone could modify Ralph's code to add the volatile integer variable intCount. It would be incremented every time the ISR runs, and printed out, and then reset, each time the temp value is printed. Basically this would tell you how many interrupts had to be serviced between two detents. For a perfect encoder it should be 4, but I'd like to get an idea of what's actually happening - how much bouncing is going on. A while back I worked on a version that had the ISR disable the interrupt that just triggered the current interrupt, and enable the interrupt on the other pin. That would prevent the bouncing from even triggering interrupts, and reduce overhead for this to almost nothing. But things get complicated when you change direction because the interrupt is enabled on the wrong pin.
Oh, and for the 328P, all I/O pins can do pin change interrupts. it's only D2 and D3 that can also do specific edges, and each with its own interrupt vector. For the other pins, each port has one pin change interrupt vector, and you set a mask register to enable specific pins.
I tried the disabling of the interrupt that had just fired some time ago (well, several years ago), Sherman. The problem was knowing when to turn it back on. If it is a specific length of time it was easier just to ignore the extra pulses in the ISR.
Regarding pin change interrupts, I felt they were too complicated for beginners; hardware interrupts are easier to understand. However, there are several libraries that handle pin change interrupts and abstract the complexity of pin change interrupts away.
The idea is to enable interrupts on one pin at a time. So assuming pins A and B, when A goes low and triggers an interrupt, within the ISR you disable further interrupts on A, and enable interrupts on B. B changed state some time ago and should be stable. Any bouncing on A produces no interrupts at all. Then when B changes state, you disable B and re-enable A. This works fine until you change direction. The problem there is that the pin with its interrupt enabled isn't the pin that changes state. I've worked out a way to deal with that - by expanding the lookup table to 32 entries, with the extra bit being which pin has its interrupt enabled, and logically it works fine. Some of the extra table entries are +2 and -2 to provide for a change in direction.
But I need to do further work on what to do when an imposslble transition occurs, and can't do that until the encoders arrive from Bangood. If I come up with anything useful, I'll send you email. We've exchanged emails before (I'm only Sherman Peabody here).
Anyway, my hope was to find a way to get acceptable performance with literally only four interrupts per detent no matter how much bouncing takes place. It's never going to be perfect because switches do sometimes continue to bounce after they should have settled down, or because the rotation speed is just too fast. But if I make any progress, I'll be in touch.
I'll await your (possible) email then!
Hi to all! I did downloaded and compiled the sketch and after minor tweks everything is working on a wemos d1 mini. The only line that I could'nt figure out was : Serial.print(rotationValue < 1 ? "L" : "R"); Could someone please translate it to me? Best regards
Well done on getting it running. 👍
That line of code is a ternary operator and means:
if the rotationValue is less than 1 print "L" otherwise print "R" on the Serial output.
(condition) ? expressionTrue : expressionFalse;
Shorthand for an if...else statement. Very useful construct to know and after a while you will use it in preference to the usual, verbose if...then construct.
Ralph, I got a Lomvum T28B some time ago. It's great but I don't remember the price I paid!
I suspect the price was so high your are unintentionally blocking the memory! Today, this 6000 count multimeter is about £20-25 from Amazon, probably less from China (allowing for VAT etc),
amzn.to/3AT8ZZW
Tried to put some details in about UniT meter and another type of rotary encoder but seems I've been locked out.
Sorry to hear that, Jorgo. But this comment is here so what happened to the others? Try replying to this one and see if it works.
@@RalphBacon mmm...something not working...replied yesterday but same thing, no show.
Yet today ok....no worries....