Author Topic: Having many rotary encoders.  (Read 7264 times)

0 Members and 1 Guest are viewing this topic.

Offline paulcaTopic starter

  • Super Contributor
  • ***
  • Posts: 4046
  • Country: gb
Re: Having many rotary encoders.
« Reply #25 on: January 27, 2023, 12:06:01 pm »
I particularly like the idea of using these in a SPI chain, as SPI slaves, with buffer mode enabled (CTRLB.BUFWR set to 1).  Each encoder would yield a 24-bit record, where the first byte is a 6-bit decisecond counter describing how long the button has been pressed, clamped to 63, starting at 1, with two MSB always 0b10.  The next two bytes are the encoder state as a 16-bit unsigned integer.  The slave chain works like a shift register, and the MCU can find out the number of encoders connected at run time from the first byte in each 24-bit record.  Each encoder state is latched on a suitable transition of the /SS, noting that there must be a couple of milliseconds between that and the first clock pulse, so that all encoders have time to latch their state before the SPI clock starts cycling.

I have looked into "exotic" SPI bus architectures a few times for various things.  The main draw back of custom bus signalling as you suggest, is the timing requirements.  I needed microsecond timing, but once you try and add "grace time" for slaves to respond to the SS line and tight timing goes out the window.  Once tight timing is gone, the corner cases all come out of the shadows until you decide that you can afford to use more than one SPI bus and KISS principle.

SPI bus chaining, I have seen it in some advanced SPI use cases with the MOSI leaves the master to the first slave, which at the same time, in it's slot sends the last buffer out it's while receiving the current on the MISO.  Each in turn along the line do this.  I can see this means you only need 1 SS line.  It also adds latency. 

In terms of HID speeds none of that really matters, if each SPI transaction takes 1ms and the chain is 6 long it's still an order of magnitude below perceivability.

Tangent question, but what is the next logical step up from SPI?  I know there are parallel GPIO connection approaching, but is/are there many established/standard protocols for linking multiple devices more tightly that SPI?

I'm thinking at one extreme you could implement a whole sub bus with address and data.  However that might be OTT and pointless. 

What might be a better approach is using something Quad SPI and the endpoints support a section of "shared memory" which is mapped and broadcast via the QuadSPI bus to all members.  The result "in code" is a section of memory that if you write to it, automagically in a whirlwind of concurrency traps appears on all other devices.

What other options lie between SPI and PCI/ISA or custom full bus?
« Last Edit: January 27, 2023, 12:12:41 pm by paulca »
"What could possibly go wrong?"
Current Open Projects:  STM32F411RE+ESP32+TFT for home IoT (NoT) projects.  Child's advent xmas countdown toy.  Digital audio routing board.
 

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3146
  • Country: ca
Re: Having many rotary encoders.
« Reply #26 on: January 27, 2023, 02:22:21 pm »
Interrupts on GPIO pins is asking for trouble. Don't do that. Especially encoders can get flaky and then the whole thing comes crashing down. But still you can use GPIOs. But sample these under a timer interrupt while using a switch matrix. That is how it is done in test equipment like oscilloscopes with rather slow microcontrollers... Easy peasy for a modern day microcontroller.

Yes, that's the best way for user-operated knobs. They are very slow. You may need to debounce them though. Create a timer interrupt. Inside the ISR, read the whole GPIO port (or two) and use bitwise operations to process them all at once. You can easily do a dozen, even with a slow MCU.
 

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3146
  • Country: ca
Re: Having many rotary encoders.
« Reply #27 on: January 27, 2023, 02:30:55 pm »
What other options lie between SPI and PCI/ISA or custom full bus?

CAN. It can handle hundreds of devices, long wires, provides "free" arbitrage, priorities, error handling. In modern cars everything is CAN.
 
The following users thanked this post: paulca

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6255
  • Country: fi
    • My home page and email address
Re: Having many rotary encoders.
« Reply #28 on: January 27, 2023, 02:51:50 pm »
I particularly like the idea of using these in a SPI chain, as SPI slaves, with buffer mode enabled (CTRLB.BUFWR set to 1).  Each encoder would yield a 24-bit record, where the first byte is a 6-bit decisecond counter describing how long the button has been pressed, clamped to 63, starting at 1, with two MSB always 0b10.  The next two bytes are the encoder state as a 16-bit unsigned integer.  The slave chain works like a shift register, and the MCU can find out the number of encoders connected at run time from the first byte in each 24-bit record.  Each encoder state is latched on a suitable transition of the /SS, noting that there must be a couple of milliseconds between that and the first clock pulse, so that all encoders have time to latch their state before the SPI clock starts cycling.

I have looked into "exotic" SPI bus architectures a few times for various things.  The main draw back of custom bus signalling as you suggest, is the timing requirements.  I needed microsecond timing, but once you try and add "grace time" for slaves to respond to the SS line and tight timing goes out the window. 
Nothing exotic here!  Well, actually, I was thinking of having the encoders latch their state at the rising edge of /SS.

In practice, you can either pulse /SS, in which case you're treating it as a latch strobe (in addition to slave select active low), or treat it as a normal slave select active low signal, in which case the slaves latch their state at the end of each read sequence.  In neither case is there any kind of tight signaling: just a minimum /SS high time.  With 16 or 20 MHz ATtiny202/204/402/404, I'd say 10µs minimum /SS high would be plenty (and lead to rock solid performance, since it corresponds to 160 or 200 cycles, several times the cycles the actual latch operation needs, even if accounting for concurrect encoder interrupts occurring – although I'd set it to higher priority than encoder interrupts anyway).

Sure, it would be nice if you had a separate latch strobe, but then you need more than 8 pins (VCC, GND, 3 for the encoders with a button, CLK, MOSI/DI, MISO/DO).  ATtiny204 has 14 pins, is in stock, and at Mouser costs about 0.67€ apiece, so you could have a separate latch strobe and simultaneous support for I²C and chained SPI with those.

(I do have an idea how the minimum could be eliminated by "pre-buffering" the initial byte to the SPI outgoing data register whenever it changes, but I'd need to verify its timing in practice before I can claim it is possible to do.)

SPI bus chaining, I have seen it in some advanced SPI use cases with the MOSI leaves the master to the first slave, which at the same time, in it's slot sends the last buffer out it's while receiving the current on the MISO.  Each in turn along the line do this.  I can see this means you only need 1 SS line.  It also adds latency.
That's why you want the latch strobe.  That sets the exact moment when the encoder is read.  True, there is latency, but let's consider for a moment.  Even if you used a 400 kHz clock (similar to fast I²C), you can read 16 encoders once every millisecond (16×24×1000 = 384,000, leaving 40µs per millisecond for the latch strobe).  Conversely, the latency is then less than a millisecond.  USB HID devices like mice and keyboards also have at least a millisecond of hardware latency (due to USB HID protocol), so I'd say it is perfectly acceptable.

Even with naïve coding, I believe up to 1 MHz SPI clock should be easy to support, maybe higher.  (I do believe the maximum SPI slave clock these ATtinys can support is half the main clock, so 8/10 MHz, but it needs to be limited it low enough so the encoder interrupts occurring during SPI transfers cause no glitches or errors to occur.)

So, no, latencies are not a problem.  The 10µs minimum latch strobe duration can be annoying, considering it is 10X clock cycles at X MHz, but anything up to a millisecond should be perfectly acceptable, as it just adds to the latency (which is a fraction of a millisecond to a millisecond).  Furthermore, repeated latching (without reading the data in between) is perfectly harmless.

If one considers the protocol, then one could just as easily support push buttons and analog potentiometers in the same bus, by e.g. using the two or three most significant bits of the 24-bit report as the report type identifier, with 000 or 111 indicating no device (achieved by a terminating resistor to ground or VCC in the MOSI/DI pin at the last device in the chain).  For any kind of button-like device, I do like the idea of it reporting the duration it has been kept pressed (which is not automatically cleared when no longer pressed, only after the report has been successfully sent and it is no longer pressed).



Another option would be to have each encoder be an I²C master, with the MCU being the I²C slave, so that whenever the encoder state changes, it sends an I²C event to the MCU (slave!); inverted to the normal situation, essentially.  One would need to assign each encoder a fixed identifier (included in the I²C data packet), basically their own 7-bit ID, repeated in the event report.  Optimally, it would also encode the type of the encoder (maybe two bits?).

However, I haven't implemented such a many-master single-MCU-slave I²C bus myself, so I don't know if there are gotchas I haven't thought of that make it impractical.  In particular, I don't know if the MCU-slave should respond to all messages on the I²C bus, with the address field indicating the sender, except when it itself is the actual master (sending control commands to a particular encoder), or if there are issues with that.

I certainly don't like the idea of having to poll each device via I²C.  It feels inefficient, somehow, to me –– a purely emotive argument, though.
« Last Edit: January 27, 2023, 02:54:13 pm by Nominal Animal »
 

Offline paulcaTopic starter

  • Super Contributor
  • ***
  • Posts: 4046
  • Country: gb
Re: Having many rotary encoders.
« Reply #29 on: January 27, 2023, 04:17:45 pm »
Mostly academic at this stage.  I have enough this time for the timers for the volume controls, for the Parameter select and value knobs they will be on a different micro and have the option of poll, interrupt or timer.

On the interrupts and bounces.  It's amazing how something really janky like making your debug logging blocking fixes bouncing pretty sharpish.  Of course its because you spend several ms waiting on the UART unaware of the bounces.

The trouble I have with that approach is of course later, in a week or two I might strip back a lot of debug calls and ... it all stops working due to bounce city.

So I'm going to take another tangent and go and write myself a Logging.h file that takes a UART (or a CDC VCOM endpoint) and gets on with it, so I don't have to run into async logging issues in ISRs every again.
"What could possibly go wrong?"
Current Open Projects:  STM32F411RE+ESP32+TFT for home IoT (NoT) projects.  Child's advent xmas countdown toy.  Digital audio routing board.
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 825
Re: Having many rotary encoders.
« Reply #30 on: January 27, 2023, 04:22:24 pm »
Quote
The kind of encoder code that breaks with bouncing this is the kind where you interrupt on say A signal and then check the state of B to see if you will increment/decrement.
I use interrupt on A or B (any edge), then read/save the other pin state in the isr, switch interrupts to the other pin, and inc/dec the encoder count if needed. The interrupt can be caused by a bouncy pin but that pin's interrupt is turned off in the isr so no repeat isr for the same pin. Reading of the other pin will/should be a stable state, and when you get to a place where both pins last state were 0 you have a step and the direction is determined by the pin that caused the interrupt. Doesn't matter if direction changed anywhere in the process, and debouncing is not needed since the bouncy pin only indicates a pin change and its irq will then be disabled.

A good test for me is to use the encoder knob index to see if I can get the same absolute position after turning the knob many times fast in either/both directions, then return the index to its original position where I should get the same absolute value. Most of the time I do get back to the same value, but there can be times where a step is missed.

Works pretty well for me, and can be used on most mcu's where you can differentiate what pin caused the interrupt.

https://github.com/cv007/NUCLEO32_G031K8_B/blob/main/Encoder.hpp
I indicate in the comments that the stm32 needs to use unique pin numbers to make this work, but that may not be the case as one could use the irq enabled state of the pins to differentiate (but if more than one encoder then that will fail to work if using 3 pin numbers that are the same). The g031 does have a lptim with encoder which would make a good test by using that at the same time and compare values. It would also be better to move the pin irq to a high priority so if another irq is using up mcu time there is less chance of a missed step.

Complete example for a tiny416-
https://godbolt.org/z/YKc99oj91


If I had a group of encoders, I would be inclined to move that over to some cheap any bit mcu with enough pins to handle the job and use i2c as the interface. This assumes one can do this quickly/easily without a lot of extra time involved, and end up with an always works result that you mostly do not have to touch again.
 

Online Peabody

  • Super Contributor
  • ***
  • Posts: 2005
  • Country: us
Re: Having many rotary encoders.
« Reply #31 on: January 27, 2023, 05:37:34 pm »
cv007 +1.  And here's my example of switching the interrupt to the other pin using a processor that doesn't even identify which pin interrupted, only the port.  With only one encoder per port you know which pin triggered the interrupt because you've only enabled interrupts on one pin.  And you know the true non-bouncy state necessary to trigger the interrupt, so you don't even have to read the pin's current value, which read would be subject to bouncing errors.  You read the other pin, which transitioned a while back, and should be stable now.

https://github.com/gbhug5a/Rotary-Encoder-Servicing-Routines/tree/master/Arduino

I'm still having trouble with the idea of using separate processors and inventing a new addressable communications protocol when a main processor with enough pins should be able to easily handle at most two encoders at a time, particularly if bouncing issues are greatly reduced through switching the interrupt enables.  And maybe hardware debouncing to make it perfect, but that's not really necessary.

 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 825
Re: Having many rotary encoders.
« Reply #32 on: January 27, 2023, 10:07:35 pm »
Quote
And here's my example of switching the interrupt to the other pin using a processor that doesn't even identify which pin interrupted

I had a mega328 example in compiler explorer that does same/similar, but am unable to test so was just exploring what was possible without specific pin flags for that popular mcu-
https://godbolt.org/z/fjTz5KncT

Quote
I'm still having trouble with the idea of using separate processors and inventing a new addressable communications protocol when a main processor with enough pins should be able to easily handle at most two encoders at a time
When you have 2 and later want to add another, and another, and now have to look for the same mcu series in a higher pin count, it may have been better to separate the procedure out to another mcu. Just depends. The communications protocol would be pretty simple and not much inventing would be needed.
 

Online Peabody

  • Super Contributor
  • ***
  • Posts: 2005
  • Country: us
Re: Having many rotary encoders.
« Reply #33 on: January 27, 2023, 11:50:34 pm »
Maybe so.   But in the original post paulca said he might need as many as 10 encoders.  Counting the pushbuttons, that's 30 pins.  It seems you could plan for that to begin with.  In any case, pin availability is the only issue I can see with doing it all in the main processor.  The processing overhead should not be significant because you're only servicing two encoders maximum at a time.  And contrary to other comments, I think interrupts are exactly what should be used - the more encoders you have, the more interrupts make sense.
 

Offline nctnico

  • Super Contributor
  • ***
  • Posts: 26906
  • Country: nl
    • NCT Developments
Re: Having many rotary encoders.
« Reply #34 on: January 27, 2023, 11:55:52 pm »
For such a simple task, it is completely unnecessary to use two (or more) microcontrollers. Don't make a system distributed unless you really have to. Think about what your system has to do when something fails or communication becomes garbled.

Maybe it is better to really dive into what is necessary to read an encoder. A typical encoder has like 24 to 36 pulses per rotation. How fast can you rotate it and make a precise adjustment? Maybe half a rotation per second. That is 18 pulses per second per contact. Each encoder has 2 contacts. So if you have 10 encoders in a 5 x 4 matrix (to handle 20 contacts) and want some oversampling - say 4 times- then you'd need to scan each row 18 * 4 * 4 = 288 times per second. IOW: a 500 Hz timer interrupt to scan the entire matrix is more than sufficient and on a modern microcontroller this is a tiny task.

And again: never ever use interrupts for reading stuff like contacts. Recipy for a dissaster when (not if) the contact goes flaky and you can't predict when something is happening so you get an unpredictable CPU load. A timer interrupt scanning a matrix OTOH is a well defined and constant load on a system so it is easy to test & verify the system meets timing criteria.
« Last Edit: January 28, 2023, 12:10:29 am by nctnico »
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 
The following users thanked this post: pcprogrammer

Offline pqass

  • Frequent Contributor
  • **
  • Posts: 725
  • Country: ca
Re: Having many rotary encoders.
« Reply #35 on: January 28, 2023, 03:52:53 am »
Building on nctnico's switch matrix scanning solution...
you can adapt my single switch debounce implementation here https://www.eevblog.com/forum/microcontrollers/switch-software-debounce/msg3684220/#msg3684220 (specifically, see the "debounce.ino.txt" attachment).

To summarize, setup a 200Hz timer interrupt and execute a DebouncePin() call for every switch.

In the case of a matrix, first activate a "row" where the common of 4 encoders (8 switches) are tied together, then read the 8-bit MCU port that the non-common encoder pins are attached to. Do this for 3 rows of commons (up to 12 encoders possible) whose non-common encoder pins share the same 8-bit MCU port pins (as other row's encoders).  That's just 3 "row" writes (activations) and 3 port reads per interrupt call.  And just 11 MCU pins.

The DebouncePin() function basically checks if the last 8 calls (40ms) for the given switch has settled and only changes the debounced state variable once that happens.  My AVR version of the function is compiled to 20 instructions giving about 1.25us execution @16MHz system clock.  That's 25us for 20 switches (function executions) and then 20 calls to the function, 3 writes (row activations), and 3 port reads, which should be minimal.  Additionally, for encoders, you don't really care of the state of each switch but rather the quantity being incremented/decremented.  So additional logic will need to be added for that in the interrupt.

I've adapted my single switch debounce implementation to track encoder changes. I HAVE NOT TESTED THIS YET. But you should get the gist.  Of course you can refactor the pair of vars per switch into a better datastructure and implement the 3 row activations, 3 port reads, and 20 calls logic in the timer interrupt.
Code: [Select]
// last parameter is changed only after 40ms passes (8 checks * 5ms timer interval) after the last time the button changed state.
uint8_t DebouncePin(uint8_t current_pin_state, volatile uint8_t *candidate_state, volatile uint8_t *debounced_state) {
  uint8_t hasChanged = 0;
  *candidate_state = (*candidate_state) << 1 | (current_pin_state & 0x01);
  if ((*candidate_state) == 0xff) {
    *debounced_state = HIGH;
    hasChanged = 1;
  } else if ((*candidate_state) == 0x00) {
    *debounced_state = LOW;
    hasChanged = 1;
  }
  return hasChanged;
}

static volatile uint8_t enc_sw0_tmp   = 0;    // temp key state area; only used by DebouncePin().
static volatile uint8_t enc_sw0_state = HIGH; // used elsewhere to query debounced state of button.
                                              // initialize to normally inactive state; ie. an unpressed button with pullup to Vcc.
static volatile uint8_t enc_sw1_tmp   = 0;    // temp key state area; only used by DebouncePin().
static volatile uint8_t enc_sw1_state = HIGH; // used elsewhere to query debounced state of button.
                                              // initialize to normally inactive state; ie. an unpressed button with pullup to Vcc.

static volatile uint16_t enc_quantity = 0;    // var that is incremented or decremented by the encoder.

ISR(TIMER1_COMPA_vect) {    // called 200 times per second; every 5ms; see timer_setup()
  if (DebouncePin((PINA>>(PA0)), &enc_sw0_tmp, &enc_sw0_state)
   || DebouncePin((PINA>>(PA1)), &enc_sw1_tmp, &enc_sw1_state)) {
    if (enc_sw0_state && !enc_sw1_state)  enc_quantity++;
    if (!enc_sw0_state && enc_sw1_state)  enc_quantity--;
  }
  // add similar to previous and static vars (see above) for every encoder to debounce.
}

// use enc_quantity in main code for current value of encoder.


On second thought,
using a 24 pulse per rotation encoder at 1 rotation per second means that there is a [single] switch change every 21ms.  A switch needs to finish bouncing and stick to one state for 8 timer interrupts. Assuming we want to test the state at the 1/3rd way point (6.6ms; can't do half-way as the other channel changes then) and assuming half of that is bounce-time, then 8 timer interrupts need to execute in 3.3ms. Therefore, the timer interrupt frequency needs to be increased to 2.4Khz (every 416us).  Assuming 40us to execute a single timer interrupt (checking 20 switches and updating encoder quantity vars), then scanning would consume 10% of available MCU time.

« Last Edit: January 28, 2023, 05:45:47 am by pqass »
 

Offline pcprogrammer

  • Super Contributor
  • ***
  • Posts: 3695
  • Country: nl
Re: Having many rotary encoders.
« Reply #36 on: January 28, 2023, 06:35:15 am »
For such a simple task, it is completely unnecessary to use two (or more) microcontrollers. Don't make a system distributed unless you really have to. Think about what your system has to do when something fails or communication becomes garbled.

I totally agree with this. Don't over complicate things. The premise here is mainly volume control. How fast and often is one going to adjust these. Even on old 8 bit micros keyboard scanning was done by reading a matrix, often in the main loop, and how often was a key stroke missed?

I have written code for interfacing lots of sensors and switches to midi on 8051 microcontrollers without problems. These are much slower then any modern ARM bases micro.

A simple user interface even with a display can easily run on a modern micro like a STM32F4. Yes pins might be a limit, but with a matrix it does not need that much pins to connect multiple rotary encoders, including the switches they might have. And there are variants of most STM32F micros with a lot of pins. Need pins take a STM32F407 like this one: https://nl.aliexpress.com/item/1005004950482623.html

Offline nctnico

  • Super Contributor
  • ***
  • Posts: 26906
  • Country: nl
    • NCT Developments
Re: Having many rotary encoders.
« Reply #37 on: January 28, 2023, 08:43:15 am »
On second thought,
using a 24 pulse per rotation encoder at 1 rotation per second means that there is a [single] switch change every 21ms.  A switch needs to finish bouncing and stick to one state for 8 timer interrupts.
Why would you want so many sample points? You can do with 3 or even 2 sampling points to check whether a switch is stable. I typically use 3; a switch is done bouncing after a few ms anyway. When using a scanning matrix, adding a bit of filtering in the hardware is cheap. And make sure to enable as much hysteresis as you can get from the microcontroller.
« Last Edit: January 28, 2023, 08:45:25 am by nctnico »
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 

Offline 8goran8

  • Contributor
  • Posts: 29
  • Country: cs
Re: Having many rotary encoders.
« Reply #38 on: January 28, 2023, 09:03:58 am »
One of the solutions, for this task, can be a Microchip 8-bit or 16-bit microcontroller that has a Configurable Logic Cell in it, e.g. PIC18F25/45/55Q43 family with 8 CLC.
In my opinion, that would be a very good combination of software and hardware solutions to this problem.
AN2133 and AN2805 documents from Microchip can be a good starting points for using CLC.
« Last Edit: January 28, 2023, 09:09:16 am by 8goran8 »
 

Offline paulcaTopic starter

  • Super Contributor
  • ***
  • Posts: 4046
  • Country: gb
Re: Having many rotary encoders.
« Reply #39 on: January 28, 2023, 10:46:23 am »
If ALL I was doing was running 4, 6, 10 rotary encoders obviously a single micro.  However I'm not only running the encoders, they are meerly the tactile interface.  The bulk of the code will be in the actual graphical user interface on a TFT, with touchscreen.  The twist there is the TFT will happily take 40Mbit/s +, but the touch screen absolutely wont.  Easiest way, path of least resistance is 2 SPI periphs.  So the pins go quickly.

I have an F405 in 144 pin format and 160Mhz. pretty sure I can get 10 encoders, 3 or 4 SPI, a couple of I2C and several TFTs and gadgets and still have pins spare.  I'm not denying that is a possibility.

There are other non-functional aspects though.

1.  A contained 4 encoder PCB component would be quite reusable.
2.  A separate PCB for the front panel is hardly a unique technique.
3.  Saves on wiring or the need for ribbon connectors to the front panel

We have discussed my engineering technique in the past and I won't flog a dead horse, except to say you won't get the big iron techniques out of me, I'm too long in the tooth.  So "Divide and conquer", "Isolation of responsibility", "Facade/Encapsulate complexity".    That doesn't mean farming everything out to a dev board with an MCU and trying to implement the Spring embedded framework!  It just means if there are clear divisions or layers in functionality, I personally and professionally like to draw lines of isolation and contract of interface etc.  Even if that wastes a little outright performance or efficiency (where they are not important!).

If I follow the 4xEncoder approach with an F103, take it to PCB I will have a small pile of boards which I can lift one of should I need any encoders in a project or on a breadboard.  I like that idea!
« Last Edit: January 28, 2023, 10:49:25 am by paulca »
"What could possibly go wrong?"
Current Open Projects:  STM32F411RE+ESP32+TFT for home IoT (NoT) projects.  Child's advent xmas countdown toy.  Digital audio routing board.
 

Offline RoGeorge

  • Super Contributor
  • ***
  • Posts: 6202
  • Country: ro
Re: Having many rotary encoders.
« Reply #40 on: January 28, 2023, 11:00:31 am »
Buy a lot of the cheapest computer mice, in bulk, a lot of USB hubs and a raspberry PI or alike cheapest Linux SBC.

Plug all together and use the wheels from the mice as rotary encoders.  Bonus, a lot of switch buttons and swipe sensors from the optical sensor inside each mouse.  And it's a scalable solution.  :D

Offline pqass

  • Frequent Contributor
  • **
  • Posts: 725
  • Country: ca
Re: Having many rotary encoders.
« Reply #41 on: January 28, 2023, 06:36:47 pm »
On second thought,
using a 24 pulse per rotation encoder at 1 rotation per second means that there is a [single] switch change every 21ms.  A switch needs to finish bouncing and stick to one state for 8 timer interrupts.
Why would you want so many sample points? You can do with 3 or even 2 sampling points to check whether a switch is stable. I typically use 3; a switch is done bouncing after a few ms anyway. When using a scanning matrix, adding a bit of filtering in the hardware is cheap. And make sure to enable as much hysteresis as you can get from the microcontroller.

You're probably right.  But the algorithm came from http://www.ganssle.com/debouncing-pt2.htm (bottom of the page) and was targeting shitty momentary switches with 10's of ms bounces.  Encoders need to be better than that otherwise they're useless.  One could modify my implementation to mask-off the top 4 bits before the comparison (eg:  "if (((*candidate_state)|0xf0) == 0xff)...") and lower the timer interrupt frequency by half (to 1KHz).

My implementation assumes no hardware filtering since I assumed up to 12 encoders (3 rows of 4) can be multiplexed on one 8-bit port + 3 row activation pins.  I think caps on the 8-bit port will just muddle things.

So, for giggles I decided to get the code working on an Arduino.  See attached enc_debounce.ino.txt.

Also, you can see the raw+debounced waveforms. See raw.png where GREEN/BLUE is the raw encoder switches that are processed into YELLOW/RED debounced state.  Notice it takes about 100us to 300us to settle.

And, you can see in cw-ccw-intr-zoom.png debounced switches (YELLOW/RED) on the left with zoomed in in-interrupt indicator (BLUE) and enc_quantity var change indicator (GREEN).  It takes 8us to service 2 switches (one encoder). And the GREEN blip shows when the encoder is rotated CW or CCW.  Keep in mind that the debounced state (YELLOW/RED) is changed in the main line so it lags when it actually changed in the interrupt.




 

Offline RoGeorge

  • Super Contributor
  • ***
  • Posts: 6202
  • Country: ro
Re: Having many rotary encoders.
« Reply #42 on: January 28, 2023, 06:50:34 pm »
The settle time is long, but the glitches are high frequency.  Hardware RC debouncing should help a lot.

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3146
  • Country: ca
Re: Having many rotary encoders.
« Reply #43 on: January 28, 2023, 07:13:20 pm »
For encoders, you often can skip debouncing. When the contacts bounce, you would perceive this as the encoder rotating back and forth very quickly. Therefore, if you're only interested in the position, then you would get the correct position after the contacts stop bouncing anyway.

In contrast, for mom buttons, bouncing would be perceived as multiple presses, so debouncing is a must.
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14464
  • Country: fr
Re: Having many rotary encoders.
« Reply #44 on: January 28, 2023, 07:20:11 pm »
For encoders, you often can skip debouncing. When the contacts bounce, you would perceive this as the encoder rotating back and forth very quickly. Therefore, if you're only interested in the position, then you would get the correct position after the contacts stop bouncing anyway.

That completely depends on the encoder's construction.
Unfortunately, many cheaper encoders do have significant bouncing that can be read as more pulses in one direction that was intended by the user, if you don't debounce. Very common, and particularly problematic if you use encoders with detent, for which the user expects only one "increment" for one detent.

 

Offline langwadt

  • Super Contributor
  • ***
  • Posts: 4422
  • Country: dk
Re: Having many rotary encoders.
« Reply #45 on: January 28, 2023, 07:27:00 pm »
For encoders, you often can skip debouncing. When the contacts bounce, you would perceive this as the encoder rotating back and forth very quickly. Therefore, if you're only interested in the position, then you would get the correct position after the contacts stop bouncing anyway.

That completely depends on the encoder's construction.
Unfortunately, many cheaper encoders do have significant bouncing that can be read as more pulses in one direction that was intended by the user, if you don't debounce. Very common, and particularly problematic if you use encoders with detent, for which the user expects only one "increment" for one detent.

for that to happen both lines have to bounce at the same time, or the decoding done wrong


 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 825
Re: Having many rotary encoders.
« Reply #46 on: January 28, 2023, 08:32:07 pm »
Quote
When the contacts bounce, you would perceive this as the encoder rotating back and forth very quickly
If you use the method I gave examples for, the 'bouncy' pin is used only to indicate a state change- doesn't matter what transition took place on that pin (H-L/L-H) because you are not relying on its current state for anything else. Once you get to the isr the 'bouncy' pin irq is disabled and you read the stable pins state (saved as 'last state') and enable its pin irq. It then requires a transition on the now stable pin to get to the next isr, and does not matter if direction change takes place at that point because we are now only looking for the change on the now stable pin and ignoring the pin which may change again due to bounce or direction change. When you get to either pin's isr, you process as stated and if the last state of both pins was 0 you have a valid step (I describe them both as last state, but the stable pin state was just read in the isr).

You end up with 4 irq's per step, and only 4 (excluding direction change). If you can turn the encoder at 20 steps/sec that will take 80 irq's/sec, and if not turning no irq's take place so no mcu time is used. The better measurement would be how fast can you ever get from one step to the next, and can you process 4 pin irq's in that time. Using the previous value of 20 steps/sec and assuming one single step in that process could be twice as fast (true or not), that would require being able to process 4 isr's in 12.5ms for an extreme single case.

The problem I see when trying to read the 'bouncy' pin state is the only reliable info you can get is that it changed, and its current state has little meaning because it can be either state due to bounce. You can work your way around it via a time component, but I think its easier to simply ignore the state of the pin that changed so do not have to deal with bounce at all and any time component is no longer needed. The isr's will be short, so a higher priority irq (if available) is desired to lessen any missed steps, but probably makes little difference in most cases.
 

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3146
  • Country: ca
Re: Having many rotary encoders.
« Reply #47 on: January 29, 2023, 12:17:08 am »
Unfortunately, many cheaper encoders do have significant bouncing that can be read as more pulses in one direction that was intended by the user, if you don't debounce. Very common, and particularly problematic if you use encoders with detent, for which the user expects only one "increment" for one detent.

If B is stable then rising edges on A are one direction and negative edges on A are the other direction. More moves in one direction would mean more rising than falling edges (or the opposite), but this is impossible.

If B can start bouncing in the middle of the stable state (e.g. some dirt on contacts) then of course all bets are off and you probably may trow such encoder away.
 

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3146
  • Country: ca
Re: Having many rotary encoders.
« Reply #48 on: January 29, 2023, 12:36:41 am »
The problem I see when trying to read the 'bouncy' pin state is the only reliable info you can get is that it changed, and its current state has little meaning because it can be either state due to bounce. You can work your way around it via a time component, but I think its easier to simply ignore the state of the pin that changed so do not have to deal with bounce at all and any time component is no longer needed. The isr's will be short, so a higher priority irq (if available) is desired to lessen any missed steps, but probably makes little difference in most cases.

But you lose a bit of precision. When pin B is stable, pin A may be low or high (depending whether it is left of the contact's edge or right of it). These are different positions but you don't see the difference, So, your resolution is half of the resolution you could've got with conventional method.

With conventional method and debouncing, you know the exact position once the debouncing is done, but it takes a delay and your position will not get updated until debouching is done.

Or, if you use conventional method and ignore bouncing completely, there will be a bouncing chaotic period where it is exactly on the contact's edge. During such period, you may think that the encoder is right of the contact's edge while it is still on the left, or vise versa. But this uncertainty will disappear as soon as you move away from the contact's edge.
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 825
Re: Having many rotary encoders.
« Reply #49 on: January 29, 2023, 05:44:41 pm »
Quote
But you lose a bit of precision. When pin B is stable, pin A may be low or high (depending whether it is left of the contact's edge or right of it). These are different positions but you don't see the difference, So, your resolution is half of the resolution you could've got with conventional method.
I guess I am using a 24 position encoder with detent, so precision is already about as high as usable for human interaction. I probably misspoke about the information to be had from the bouncy pin- with what I am doing, the bouncy pin still cannot be read reliably, but one can deduce its state by knowing its last state (which was a stable read). So the bouncy pin state can be deduced by simply inverting its stored last state value in its isr (we are in its isr because the pin changed).

Here is a modification to a previous example to use all 4 states with the same alternating pin irq method-
https://godbolt.org/z/d3rhaE8rj
the switch/case could probably be simplified although any compiler produced code to do this would probably end up taking mostly the same time.

I tested this example as I did the previous example, and it works but a 24 position encoder w/detent is not a great fit for doing this since there is already a detent and those detent stops are a little touchy, plus the extra resolution is unusable. It still holds absolute position pretty well. My original example/method actually ends up with counting a step at the 'third' irq, so the touchy detent stops do not end up doing anything (to the count).

edit- a little less verbose isr-
https://godbolt.org/z/sWKj8vzvW
« Last Edit: January 29, 2023, 08:42:02 pm by cv007 »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf