Example for working with a rotary encoder?

Hi,

Pls anyone will share an example which demostrates how to work with a rotary encoder (and groups)?

Thanks,
Kaiyuan

Hi @dukeduck,
Do you mean something like this?

A C code example would be easier to find, and it should be easy to convert it to Micropython.
@embeddedt / @kisvegabor - could you suggest usage examples (in C) for “rotary encoder (and groups)”?

Thanks @amirgon!

I found this post (ESP32 with Rotary Encoder - Simple example for Newbie) which is related to my question. But I’m pretty new to programming and don’t have a background in C language, by looking at the code in that post I’m a bit confused by the read_encoder function which returns a boolean - how is the turning direction and the pressing of the button data passed to the littlevgl?

Thanks,
Kaiyuan

  data->enc_diff = diff - last_diff;;
  data->state = btn_state;

Turning direction and value is set on enc_diff and button state is set on state.
The return value false only means that there is no more data to read.

It should be very easy to translate this function to Micropython. Give it a try and let me know if you get stuck.

Hello,
recently worked on this topic. See class below (file “encoder.py”):

"""
code for handling encoders

"""

from machine import Pin
import utime

PERIOD_AQR = 70  # "fast gear" acquire period in ms
TRESH = 4  # "fast gear" speed treshhold
FASTINC = 60  # "fast gear" step-with


class IncRotEc11:  # create class "IncRotEc11"
    """
    class for handling incremental rotary encoders like e.g. ALPS EC11 series
    when turning slow position inc- decrements by 1
    when turning fast position inc- decrements by FASTINC

    __init__ arguments
        IncRotEc11(PinA, PinB, PinSwitch, MaxPos, InitPos)
            PinA
                pin number channelA
            PinB
                pin number channelA
            PinSwitch
                pin number switch
            MaxPos
                position maximum value
            InitPos
                position init value

    methods
        .update()
            must be called in main loop or via timer interrupt

    variables
        .position
            0 ... MaxPos
            init value = InitPos
        .switch.value()
            0 = pressed
            1 = not pressed
    """
  

    def __init__(self, PinA, PinB, PinSwitch, MaxPos, InitPos):  # method is automatically called when new instance of class is created
        self.MaxPos = MaxPos
        self.ChA = Pin(PinA, Pin.IN, Pin.PULL_UP)  # encoder channel A, PinA, enable internal pull-up resistor
        self.ChB = Pin(PinB, Pin.IN, Pin.PULL_UP)  # encoder channel A, PinB, enable internal pull-up resistor
        self.state = self.ChA.value() + (self.ChB.value() << 1)
        self.state_last = self.state
        self.switch = Pin(PinSwitch, Pin.IN, Pin.PULL_UP)  # encoder pushbutton, PinSwitch, enable internal pull-up resistor
        self.pos = (InitPos * 2) + 1
        self.position = InitPos
        self.position_lastsample = self.position
        self.time_lastsample = utime.ticks_ms()
        self.table = [[0, 1, -1, 0], [-1, 0, 0, 1], [1, 0, 0, -1], [0, -1, 1, 0]]  # incremental rotary encoder states
        self.io27Val = 0# DEBUG_ONLY
        self.io27 = Pin(27, Pin.OUT, value=0)# DEBUG_ONLY create object, test output, GPIO27

    def update(self):
        self.state = self.ChA.value() + (self.ChB.value() << 1)
        i = (self.table[self.state_last][self.state])
        self.state_last = self.state
        self.pos += i
        self.position = self.pos // 2  # filter out odd and unstable position values that may exist
        self.timenow = utime.ticks_ms()
        if utime.ticks_diff(self.timenow, self.time_lastsample) > PERIOD_AQR:  # fastgear?
            self.posdiff = self.position - self.position_lastsample
            if abs(self.posdiff) > TRESH:
                self.fastgear()
            self.position_lastsample = self.position
            self.time_lastsample = self.timenow
        if self.position > self.MaxPos:  # overflow?
            self.reset()
        elif self.position < 0:  # underflow?
            self.set()
        self.io27Val = not self.io27Val# DEBUG_ONLY toggle test output to enable measuring length of program cycle
        self.io27.value(self.io27Val)# DEBUG_ONLY


    def reset(self):
        self.pos = 0
        self.position = 0
        self.position_lastsample = self.position

    def set(self):
        self.pos = (self.MaxPos * 2) + 1
        self.position = self.MaxPos
        self.position_lastsample = self.position

    def fastgear(self):
        if self.posdiff > 0:
            self.position = self.position + FASTINC
        else:
            self.position = self.position - FASTINC
        self.pos = (self.position * 2) + 1
        self.position_lastsample = self.position

Usage:

encoder = IncRotEc11(PINA, PINB, PINSWITCH, MAXPOS, INITPOS)  # create new instance of class "IncRotEc11"

encoder.update()  # read encoder, must be called in main loop or via timer interrupt

switch = encoder.switch.value()  # read encoder switch value

pos = encoder.position  # read encoder position

encoder.__init__(PINA, PINB, PINSWITCH, MAXPOS, INITPOS)  # set encoder to init-position

encoder.reset()  # reset some encoder internals

The code works fine as long as function encoder.update is called with a frequency that fits the speed of the turning encoder. At the moment I have the problem that another functionality takes to much time and can not be interrupted, so that missing steps arise. But that’s another problem I have to treat in a separate posting. Beware to delete the debug-code that may be in the sources!

Good luck!
Peter

1 Like

@sengp Did you register this as an input device for lvgl? (lv.indev_drv_register) If you did it could be useful to see that code as well.

@amirgon Posted the project to:
My projects

I have following design problem:
When turning the rotary encoder the alarm setting has to be updated. But a display update via “lv.scr_load(scr)” takes 50…120ms. During that time the main loop of the program pauses and the rotary encoder has missing steps, it still works but it’s not a satisfying behavior. Will it improve when registering it as an input device? I’ll read the docs and claim help if I don’t succed.

best regards,
Peter

Very nice.
I recommend, however, pushing your code to github instead of sending a zip file. It will be more accessible since most people will not risk downloading a zip file from a public forum.

Why does it take so long?
My experience is that full screen update can be done in ~30ms and even less when running ESP32 on 240Mhz, but it depends on what exactly you are rendering.

It is a better design to register an input device to lvgl. But I’m not sure this will solve this problem.
What you should do, is implement an interrupt handler that tracks changes in your input and updates the aggregated change since last lvgl polling.
An interrupt handler will run immediately even when “the main loop of the program pauses”.

Already tried to to implement the reading of the rotary encoder via a callback function. Did it by using a periodical timer interrupt.

tim = Timer(-1)
io27Val = 0
io27 = Pin(27, Pin.OUT, value=0)  # create object, test output, GPIO27

def asd():
    global io27Val
    io27Val = not io27Val  # toggle test output
    io27.value(io27Val)

tim.init(period=10, mode=Timer.PERIODIC, callback=lambda t:asd())  # timer interrupt

But it did not succeed. During “lv.scr_load(scr)” the interrupt is not executed any more or just in an erratic way. Seems that the SPI transmission uses higher interrupt priority. Do you know a way to change interrupt priority on ESP32?
Or exists a way to just update a small part of the display content (the part containing the Alarm-time)?
Did you really see display updates lasting 30ms?

best regards

Peter

This is not what I meant.
You need an interrupt handler that is triggered by your hardware, not by a timer.

I’ll try someting like the following code, hope it can be done on ESP32:

configure an irq callback
p0.irq(lambda p:print(p))

Will make a reply if it works (or not)

best regards

Peter

That’s how LittlevGL works internally. It only updates the changed objects.

I find it weird that lv.scr_load takes 30-50 ms. No rendering takes place inside that function; it merely changes the active screen and invalidates all objects on it (code here). My guess is that something else is taking up that time, or your measurement is incorrect.

Thanks @amirgon.

So, enc_diff should be integer? for example, a positive value means “next”, a negative value means “previous”? Then, how to pass “LV_KEY_LEFT” “LV_KEY_RIGHT” command to the lvgl?

Thanks,
Kaiyuan

@sengp Thanks Peter for sharing your project - I like the UI of your alarm, and the seg-7 font is awesome. I was trying to implement this using event callback as described in the docs, I think I’m getting closer.

Kaiyuan

Hello,
now implemented it via interrupt handler (see down), which resulted in minor improvements. Seems that the SPI interrupt has higher priority, so the Pin interrupt is delayed/not executed during TFT update. On ESP32 Pin interrupt priority is not configurable. This results in a poor performance of the rotary decoder. It can be used, but maybe micropython is not the favorite language to implement such things…
But LittlevGL can also be used in C :slight_smile: and it was great fun to have some experience with it and micropython too.

Exists no way to update just a small part of the TFT-display, containing only the digits affected by the rotary-decoder, in much less time than described in the previour posts?

Attached see the micropython rotary encoder class, feel free to use it.

best regards,

Peter

"""
code for handling encoders

Author: sengp
Date: 2020-01-24

"""

from machine import Pin
import utime


class IncRotEc11:  # create class IncRotEc11
    """
    class for handling incremental rotary encoders including push momentary switch
        - based on PIN interrupt
        - debounce functionality
        - fastgear functionality
        - no need for timer interrupt or polling
    Code implemented following these sources:
        https://github.com/miketeachman/micropython-rotary
        http://www.buxtronix.net/2011/10/rotary-encoders-done-properly.html
        https://github.com/buxtronix/arduino/tree/master/libraries/Rotary

    Hint: rotation speed limited by micropython's low performance interrupt capabilities

    optimized for decoders like e.g. ALPS EC11 or BOURNS PEC11L series:
        - e.g. 30 detents/revolution, channel A and B each generating 15 pulses/revolution
        - channel A -> bit0, channel B -> bit1
        - cyclic AB values are (CW: left to right, CCW: right to left): ...,0,1,3,2,0,1,3,2,0,1,3,2,0,1,...
        - mechanical detents at AB value 0 and 3
        - suggested circuit to connect rotary encoder including switch:
            - A/B and switch common connected to GND
            - A/B connected via 10K pullup to VCC
            - A/B channel output low pass filtering via 10K/10nF, see BOURNS PEC11L datasheet
              Hint: low pass filtering is highly recommended to minimize interrupt frequency
            - do not enable internal pullup/pulldown for A/B channel at MCU input
            - at switch input internal pullup at MCU input enabled
    when turning slow:
        position inc- decrements by 1 in range of 0...MaxPos
    when turning fast:
        position inc- decrements by FAST_INC in range of 0...MaxPos

    __init__ arguments
        IncRotEc11(PinA, PinB, PinSwitch, MaxPos, InitPos)
            PinA
                pin number channel A
            PinB
                pin number channel B
            PinSwitch
                pin number switch
            MaxPos
                position maximum value
            InitPos
                position init value

    methods
        .update()
            called via PinA/PinB.IRQ_RISING or PinA/PinB.IRQ_FALLING interrupt
        .set(position)
            position
                0 ... MaxPos

    variables
        .position
            0 ... MaxPos
            init value = InitPos
        .switch.value()
            0 = pressed
            1 = not pressed
    """

    def __init__(self, PinA, PinB, PinSwitch, MaxPos, InitPos):  # method is automatically called when new instance of class is created
        self.MaxPos = MaxPos
        self.ChA = Pin(PinA, Pin.IN, None)  # encoder channel A, PinA, no internal pull-up or pull-down resistor
        self.ChB = Pin(PinB, Pin.IN, None)  # encoder channel A, PinB, no internal pull-up or pull-down resistor
        self.enable_ChA_irq(self.update)
        self.enable_ChB_irq(self.update)
        self.ABval = self.ChA.value() + (self.ChB.value() << 1)
        self.state = self.ABval
        self.switch = Pin(PinSwitch, Pin.IN, Pin.PULL_UP)  # encoder pushbutton, PinSwitch, enable internal pull-up resistor
        self.position = InitPos
        self.time_lastsample = utime.ticks_ms()
        self.inc = 1
        self.io27Val = 0  # DEBUG_ONLY
        self.io27 = Pin(27, Pin.OUT, value=0)  # DEBUG_ONLY create object, test output, GPIO27

    def enable_ChA_irq(self, callback=None):
        self.ChA.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=callback)

    def enable_ChB_irq(self, callback=None):
        self.ChB.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=callback)

    def update(self, pin):
        # state machine output values and masks
        CW = 0x10  # output indicator CW step
        CCW = 0x20  # output indicator CCW step
        STATE = 0xF  # state mask

        # rotary encoder states
        START0 = 0x0  # detent
        CW1 = 0x1  # state clockwise next START0
        CCW2 = 0x2  # state counterclockwise next START0
        START3 = 0x3  # detent
        CW2 = 0x4  # state clockwise next START3
        CCW1 = 0x5  # state counterclockwise next START3
        END3C = START3 | CW  # end state clockwise reached from START0, output CW
        END3CC = START3 | CCW  # end state counterclockwise reached from START0, output CCW
        END0C = START0 | CW  # end state clockwise reached from START3, output CW
        END0CC = START0 | CCW  # end state counterclockwise reached from START3, output CCW

        table = [
            # state transition table, inputs: ABval, state
            #  |------------ ABval -------------|
            #  |    0       1       2       3   |
            #  |------ --- next state ----------| |-- state --|
                [START0,    CW1,   CCW2, START3],   # START0
                [START0,    CW1, START0,  END3C],   # CW1
                [START0, START0,   CCW2, END3CC],   # CCW2
                [START3,   CCW1,    CW2, START3],   # START3
                [END0C,  START3,    CW2, START3],   # CW2
                [END0CC,   CCW1, START3, START3]]   # CCW1

        FAST_PERIOD = 25  # "fast gear" acquire period in ms
        FAST_INC = 10  # "fast gear" step-with

        self.ABval = self.ChA.value() + (self.ChB.value() << 1)
        self.state = (table[self.state][self.ABval])
        if self.state > STATE:  # output indicator set ?
            # inc- or decrement
            self.timenow = utime.ticks_ms()  # get time
            if utime.ticks_diff(self.timenow, self.time_lastsample) < FAST_PERIOD:  # fastgear?
                self.inc = FAST_INC  # fastgear
            else:
                self.inc = 1  # step by step
            self.time_lastsample = self.timenow
            if (self.state & CW) != 0:  # clockwise ?
                self.position += self.inc  # increment
            else:
                self.position -= self.inc  # decrement
        self.state = self.state & STATE  # remove output indicators from state
        if self.position > self.MaxPos:  # overflow?
            self.position = 0
        elif self.position < 0:  # underflow?
            self.position = self.MaxPos
        self.io27Val = not self.io27Val  # DEBUG_ONLY toggle test output to show execution of IRQ
        self.io27.value(self.io27Val)  # DEBUG_ONLY

    def set(self, pos):
        if (pos >= 0) and (pos <= self.MaxPos):
            self.position = pos

Thanks for sharing this, Peter.

I can see a problem with your script: You are not using hard interrupts.

hard if true a hardware interrupt is used. This reduces the delay between the pin change and the handler being called. Hard interrupt handlers may not allocate memory; see Writing interrupt handlers.

This explains the latency (performance) problem you are seeing.
What is actually happening is that the interrupt handler only schedules micropython to run your handler in the future (which can take some time) instead of immediately.

Hello,
on the micropython ESP32 port the “hard” argument is not available at class Pin, when using irq() method. The feature seems to be available on the ARM based ports but NOT on ESP32.

On the ESP32, MicroPython interrupt handlers are always scheduled for later execution by the interpreter.

So we will not get it working this way…

Found another promising attempt:
pulse count units
But the module mentioned is not included in standard micropython nor the “loboris” port mentioned. I’m already in contact with the author, maybe we can find the sources…

best regards,
Peter

You are right.
I don’t see a reason why this wouldn’t be implemented for ESP32, maybe worth opening an issue on micropython upstream repo.

This really sounds useful, and it will probably be added soon to micropython.
See this forum thread.

Another option is using the espidf module, which provides raw esp-idf micropython API.
I’m using it today to access SPI and I2S directly from micropython. (see for example the micropython ili9341 driver).

I’ve added pcnt support to espidf module (it really only required adding a single line!), so you can simply do something like:

import espidf as esp
esp.pcnt_unit_config({
  # ...configure pcnt here...
})
...
cnt_ptr = esp.C_Pointer()
esp.pcnt_get_counter_value(esp.PCNT_UNIT._1, cnt_ptr)
print(cnt_ptr.short_val)

Hello,
thanks for your investigations. Made some big steps.
Had contact with the very nice Prof. at Berkeley University I mentioned in the last post. He gave me the hint where to find the sources of his implementation of the pulse count units on ESP32.
I already included them into lv-micropython V1.12. (machine module) and compiled successfully :slight_smile:

Made some tests, the one he already delivered, and one of my own on real hardware.

–> NO MORE MISSING COUNTS DURING UPDATE OF THE DISPLAY!!!

Now i will make a module for (rotary) encoders that makes the whole thing nice to use, the abilities of the pulse count unit are very basic. Think that initial position, over/underflow and “fastgear” should be implemented. Hope to finish tomorrow.

Best regards,
Peter

Hello,

now I will sum up my results about handling rotary encoders using micropython and LittlevGL on ESP32.

Software (polling) and pin interrupt based approaches to implement a rotary encoder interface on the ESP32, all failed. Display update by LittlevGL (via “lv.scr_load(scr)”) takes about 50…120ms. During this time the encoder interface is almost blind and can not count pulses, leading to missing pulses and so unreliable behavior. The same effect will occur when not using LittlevGL, but if some other high priority functionality would need some processing time…

But there is a solution for ESP32 - named pulse counter module (PULSE_CNT):
https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/pcnt.html
In short: on ESP32 eight (8) hardware counter based interfaces for rotary (incremental) encoders can run simultaneously when the hardware is configured in a suitable way.

Fortunately someone already documented the behavior and implemented the code.

Much thanks to Mr. Boser for code, documentation and support !

At
https://people.eecs.berkeley.edu/~boser/courses/49_sp_2019/N_gpio.html#_quadrature_decoder
exists a description how these counting units can be used.

The code for implementing the feature can be found at:
https://github.com/bboser/MicroPython_ESP32_psRAM_LoBo/blob/quad_decoder/MicroPython_BUILD/components/micropython/esp32/machine_dec.c

The C-code “machine_dec.c” can be integrated into lv_micropython V1.12 or standard micropython.
Commit for “loboris” micropython port see at:
https://github.com/bboser/MicroPython_ESP32_psRAM_LoBo/commit/15806f44ad565e77967da57d67ad068001f44e28

To implement “DEC” functionality into lv-micropython 1.12 execute following steps:

  1. At directory …/lv_micropython/ports/esp32 add following line to “Makefile” at section “# List of MicroPython source and object files” after line “machine_pwm.c \”:
    machine_dec.c \
  2. Copy file “machine_dec.c” into directory …/lv_micropython/ports/esp32
  3. In file “machine_dec.c” change function call at line 47 and line 49 from
    machine_pin_get_gpio(...) to machine_pin_get_id(...)
  4. At same directory in file “modmachine.h” after line 18 insert line:
    extern const mp_obj_type_t machine_dec_type;
  5. At same directory in file “modmachine.c” after line 257 insert line:
    { MP_ROM_QSTR(MP_QSTR_DEC), MP_ROM_PTR(&machine_dec_type) },
  6. Re-compile lv-micropython

Afterwards module “machine” on ESP32 will include quadrature decoder peripheral “DEC”. Deviant from documentation the feature “dec.filter(value)” is not implemented in the sources mentioned above. But working without filter was OK and lead to good results.

Hint: counting sequence of 16-Bit pulse count unit on ESP32, may be not be as expected and over/under-run behaviour of 16-Bit signed hardware counters must be considered when developing encoder algorithms.
Up/down: 0, 1, 2, 3, 2, 1, 0, -1, -2, -3, -2, -1, 0, 1, 2, 3, …
Up: 0, 1, 2, …, 32765, 32766, 0, 1, 2, 3, …
Down: 0, -1, -2, …, -32765, -32766, 0, -1, -2, -3, …

To simplify the use of rotary encoders module “encdec.py” including class “IncRotEc11” and based on “DEC” was coded, see remarks at module/class description below:

"""
code for handling encoders attched to ESP32 16-Bit pulse counting units

Counting sequence and over/under-run behaviour of hardware counters must be considered when using encoder algorithms
Up/down:    0, 1, 2, 3, 2, 1, 0, -1, -2, -3, -2, -1, 0, 1, 2, 3, 4, ...
Up:	        0, 1, 2, 3, ..., 32765, 32766, 0, 1, 2, 3, ...
Down:       0, -1, -2, -3, ..., -32765, -32766, 0, -1, -2, -3, ...

Author: sengp
Date: 2020-02-03

"""

from machine import Pin
from machine import DEC


class IncRotEc11:  # create class IncRotEc11
    """
    class for handling incremental rotary encoders including push momentary switch
        - hardware counter based on ESP32 pulse counting unit, 16-Bit signed
        - quadrature decoder based on ESP32 pulse counting unit
        - debounce functionality based on ESP32 pulse counting unit not implemented (filter missing at class DEC)
        - no need for timer interrupt or polling
    Code implemented and based on these sources:
        https://people.eecs.berkeley.edu/~boser/courses/49_sp_2019/N_gpio.html#_quadrature_decoder
        https://github.com/bboser/MicroPython_ESP32_psRAM_LoBo/blob/quad_decoder/MicroPython_BUILD/components/micropython/esp32/machine_dec.c

    ATTENTION: Prevent position error by avoiding over/under-run of hardware counter.
               Call of method "setpos" resets hardware counter value to 0. Call of method "setpos" at appropriate location in user program is a
               suitable practice to prevent hardware-counter overrun (use actual position as argument). Use method "getcnt" to obtain
               information about counter values span from becomming critical (over/under-run), or to implement fastgear functionality

    optimized for encoders like e.g. ALPS EC11 or BOURNS PEC11L series:
        - e.g. 30 detents/revolution and channel A and B each generating 15 pulses/revolution leads to 60 impulses/revolution
          Hint: on a decoder described above, impulses must be divided by 2 to result in one impulse/detent
        - channel A -> bit0, channel B -> bit1
        - cyclic AB values are (CW: left to right, CCW: right to left): ...,0,1,3,2,0,1,3,2,0,1,3,2,0,1,...
        - mechanical detents at AB value 0 and 3
        - suggested circuit to connect rotary encoder including switch:
            - A/B and switch common connected to GND
            - A/B connected via 10K pullup to VCC
            - A/B channel output low pass filtering via 10K/10nF, see BOURNS PEC11L datasheet
              Hint: low pass filtering is recommended to prevent glitches at counter input
            - do not enable internal pullup for A/B channel at MCU input when low pass filter is assembled
            - at switch input internal pullup at MCU input enabled
        - when turning right position increments by 1 in range of 0...MaxPos
        - when turning left position decrements by 1 in range of 0...MaxPos

    __init__ arguments
        IncRotEc11(Unit, PinA, PinB, PinSwitch, MaxPos, InitPos, Filter)
            Unit
                number of pulse count unit
                    0 ... 7
            PinA
                pin number channel A
            PinB
                pin number channel B
            PinSwitch
                pin number switch
            MaxPos
                position maximum value
            InitPos
                position init value
                    0 ... MaxPos

    methods
        .getpos()
            RetVal
                0 ... MaxPos
        .setpos(position)
            position
                0 ... MaxPos
        .getcnt()
            hardware counter value
                -32765 ... 32765

    variables
        .switch.value()
            0 = pressed
            1 = not pressed
        .position
            0 ... MaxPos
    """

    def __init__(self, Unit, PinA, PinB, PinSwitch, MaxPos, InitPos):  # method is automatically called when new instance of class is created
        self.dec = DEC(Unit, Pin(PinA), Pin(PinB))
        self.switch = Pin(PinSwitch, Pin.IN, Pin.PULL_UP)  # encoder pushbutton, PinSwitch, enable internal pull-up resistor
        self.MaxPos = MaxPos
        self.offset = InitPos
        self.count = 0
        self.position = self.calcpos()

    def calcpos(self,):
        # arguments in position calculation can be positive or negative. Following line of code is valid when using Python,
        # cause Python uses the "mathematical variant" of the modulo operator. This code will fail when it is ported to e.g.
        # C, C++ or Java, cause these languages implement the "symmetrical variant" of the modulo operator, leading to
        # wrong results (in this algorithm) on negative arguments.
        return ((self.count + self.offset) % (self.MaxPos + 1))

    def getpos(self,):
        self.count = (self.dec.count() >> 1)  # read counter and divide counter value by 2 to get one impulse/detent
        self.position = self.calcpos()
        return self.position

    def setpos(self, pos):
        if (pos >= 0) and (pos <= self.MaxPos):
            self.dec.clear()
            self.offset = pos
            self.count = 0
            self.position = self.calcpos()

    def getcnt(self,):
        return self.count

Sample program using module “encdec.py”:

from encdec import IncRotEc11
import utime

"""
Quadrature Decoder Demo

Rotary encoder ChA/ChB connected to pins 13/14, switch connected to pin 12

"""


UNIT = 0
CHA = 13
CHB = 14
SWITCH = 12
MAXPOS = 59
INITPOS = 30

val = 0
oldval = val


encoder = IncRotEc11(UNIT, CHA, CHB, SWITCH, MAXPOS, INITPOS)  # create new instance of class "IncRotEc11"


while True:
    val = encoder.getpos()
    if val != oldval:
        print("Position: {:5d}      Switch: {:1d}".format(val, encoder.switch.value()))
        oldval = val
    utime.sleep_ms(300)

have fun using rotary encoders :slight_smile:

Best reagards,
Peter

2 Likes