Rotary encoders are pretty interesting pieces of technology. They’re a solid way to accurately measure rotation including the direction. [David] recently wrote some software to handle these input devices, but unlike everyone else, his application can get by on only one microcontroller pin.
Most people will use three pins to handle a rotary encoder with a microcontroller: one to handle the switch and two to handle the quadrature inputs. With only one pin left available on his project [David] had to look for another solution, and he focused on the principle that the encoder pins behaved in very specific ways when turning the shaft. He designed a circuit that generates an analog voltage based on the state of those pins. He also wrote a program that can recognize the new analog patterns produced by his rotary encoder and his new circuit.
If you’ve been stuck on a project that uses a rotary encoder because you’ve run out of pins, this novel approach may help you get un-stuck. It’s a pretty impressive feat of circuit design to boot. Just think of how many other projects use these types of input devices and could benefit from it!
[via Hackaday.io Project Page go give it a Skull!]
TLDR; A resistor ladder DAC. A nice application for this…
Thanks. I imagined it was a ladder.
Looking at only the title, I realized how they did it… without seeing the title here, I would never have thought of doing it! *tucks trick away in back pocket*
Rotary encoders are pieces of technology, the crappy one. Rotational life: 30000 minimum. Hi resistance, low current, 60RPM, for brand name product.
They are cool but not a great product.
Analog implementation is clever.
I would use some sort of IC(6 pin atmel or something), implemented full 3-wire decoding and sent digital data into desired last pin on micro (some sort of 1-wire digital line).
There are optical ones as well, should have basically permanent life unless it gets caked with dust.
But if you want something like a rotary encoder without limited travel what would you do?
A pot without end-stops might be an option but it has dead zones, and will not have a much better expected life.
Using the fancy magnetic encoders and axial magnets is cool, but you need to create the whole rotary part of the encoder.
use capacitive sensors.
Not the same as a rotary encoder, there is no shaft, and no knob, and using a round capacitive patch in a front panel is no ideal, wastes to much space, imagine a scope without knobs and with round touch zones like the iPod, it would need to be much larger to accommodate the same controls based on touch.
Of course, depends how large you want things to be. It’s an alternative. Plus, the life is given as a minimum, not average life expectancy.
Been there before on a 8-pin Freescale HC08 part. I basically share a single I/O between software based async serial half duplex Tx and Rx and thermistor for temperature sensor for a Li-ion charger. That serial port is used both for boot strap loader and for a command line interface.
The thermistor is on a voltage divider such that it is always between 2/3 Vcc and vcc and can be read off with ADC with reduced resolution. To the serial port level translation circuit, it treats it as logic high.
Take your crown because you are the new king of clever for me :)
I would argue that you could also implement this using some other pins that were used for the LCD and actually never even need that 1 pin.
True-dat. OTOH, doing-so might require somewhat significant revision of the LCD-code… whereas this hardware/software can be dropped into any project pretty much as-is.
Depends. If your LCD code does not use interrupts, the two pieces of code can be made separate. True, leaving the LCD code untouched means that the rotary code has to take care of changing the pins to input, reading them and then changing them back to output.
Right, it definitely depends… e.g. if the LCD code is in a state-machine/update() function in the main-loop which could exit/reenter midway in a transaction (maybe polling a busy-flag pin?)… Unless you’re intimately familiar with the LCD’s/other-device’s functionality/code, it’s hard to know for sure what state it will be in…
A better example, maybe, is trying to use this method with an SPI device… your new “button-reading” code might have to know to disable/reenable the SPI device’s Chip-Select (which, otherwise, might be active from boot until shut-down)… Means, again, looking into the other device’s code to know for sure.
Surely doable, likely preferable in most cases, but there’s a time-and-place for everything.
So you are trading off rewriting the bit banging LCD code which isn’t that complicated in the first place to the ~35 lines of code (ignoring comments) just to get a digital reading out of the encoder from ADC…
Former “King of Clever” here. I didn’t realize muxing LCD and encoder pins was an option and I would have strongly considered doing that if I had known. I think someone should make an LCD library that facilitates sharing the data pins with buttons, encoders or whatever. It should be as easy to drop into existing code as my approach is.
Someone in the comments nominated tekkieneet as the new “king of clever” so I nominate him.
I do have my own LCD/button library BTW… Here is the bad news: I don’t do Arduino.
The polling is done in main loop where the timer interrupt set up a flag for 100Hz (or whatever) and a low frequency 1Hz task for updates. There are alternative versions that use interrupts and even use analog inputs for sensing 3 buttons. You’ll be mad like me trying to do UI in cooperative multitasking code with state machine. It can be done, but very ugly code.
Here is something that is closed to what OP is trying to do:
My code for detecting 3 buttons that are muxed into an ADC input. Multiple buttons can be pressed and recognized at the same time and decoded as a bitmap. ADC is on timer interrupt that scans multiple channels and stored in ADC_Data[BUTTONS_IN].Result
uint16_t EEMEM ButtonValues[ADC_KEYTBL] =
{ 0x3ff, 0x1fd, 0x153, 0x0fe, 0x0ce, 0x0ab, 0x092, 0x07f };
// return raw key(s) pressed
inline uint8_t ADC_Button(void)
{ uint8_t i;
uint16_t value, key=ADC_Data[BUTTONS_IN].Result;
for(i=0;i=(value-ButtonTolerance)&&(key<=(value+ButtonTolerance)))
return(i);
}
return(0);
}
Who said “not doing Arduino” was bad news?
This almost looks like textbook code but ‘value’ is used without being defined.
ButtonValues[] is unused.
I also don’t see how this would be more efficient than just a few if statements.
I wasn’t very happy with the elegance of my code either so I’m interested in what you come up with.
There are missing code because HaD swallowed the code after angle brackets. Not my fault here.
inline uint8_t ADC_Button(void)
{ uint8_t i;
uint16_t value, key=ADC_Data[BUTTONS_IN].Result;
for(i=0;i LessThan ADC_KEYTBL;i++)
{ value=eeprom_read_word(&ButtonValues[i]);
if(key GreaterOrEqual (value-ButtonTolerance)&&(key LessOrEqual (value+ButtonTolerance)))
return(i);
}
return(0);
}
Substitute the LessThan, GreaterOrEqual, and LessOrEqual with their angled bracket symbols.
OK I get it but this approach uses more EEPROM (8 vs my 5) and swallows more registers than an interrupt handler with just a few if statements.
I also don’t see any encoder counts updated.
I was low on FLASH on a 8kB device that has a 2kB worth of V-USB library, , so I had to do everything I could putting constants in EEPROM (including text characters for a graphics based LCD) and still have code for doing actual work.
The keypress code is polled like I said by main loop. I did say *buttons* not quadrature encoder.
Surely that’s always true – you could save pins for X to use for Y, or save pins for Y to use for X, etc. etc.
Sees nothing of interest here … leaves.
If you don’t have an ADC pin, you could use a couple of these – http://www.maximintegrated.com/en/products/interface/controllers-expanders/DS2413.html
Or one of these – http://www.maximintegrated.com/en/products/digital/memory-products/DS2408.html
Okay…what’s new in this? I sent the inputs of a 12 button keypad over analog, resistor ladders are your friends, my friend.
Using a resistor ladder is not new, but nobody had published a way to exploit a resistor ladder/encoder combination. What is “new” is writing up a report on how I solved a specific problem, including supporting scope traces, sampling time determinations and specific problem statement.
Using a resistor ladder is not new, but nobody had published a way to exploit a resistor ladder/encoder combination. <– Saw this in a commercial device a few years ago and did it myself, too. I guess it's one of those tricks that some people "just do", while some are surprised about the possibility.
Btw. if you sit down with a piece of paper and think again about direction inference of the graycode you should get it right in <10 LOC.
I’ve heard of similar tricks being done in cars with stereo / cruise control buttons on the steering wheel. By MUXing them into a single analog pin, fewer wires are required to go through slip rings or a big coil to allow the wheel to turn freely. These days I imagine they just use a little micro with a CAN / LIN bus, but I’ve read about the analog technique on older cars.
Just thought … but perhaps some discrete logic to decode then create a high voltage pulse for each step CW and lower voltge for each step CCW would be easier to code, more efficient of cpu cycles and more reliable.Better yet, use pwm to creat a wide pulse for CW and narrow pulse CCW, then you don’t need an analog pin, Either would also easy determination rate of rotation if that was required.
Ugh decoding graycode in hardware is such a waste of components, really.
Just thought … but if you like doing things the hard way perhaps some discrete logic to decode then create a high voltage pulse for each step CW and lower voltge pulse for each step CCW, That would be easier to code, more efficient of cpu cycles and more reliable.Better yet, use pwm to creat a wide pulse for CW and narrow pulse CCW, then you don’t need an analog pin, Either would also allow easy determination rate of rotation if that was required. Cleanest solution, use an I/O expander to free up I/O for the encoder and leave all the kludge behind.