Pin ID on pyboard

The official pyboard running MicroPython.
This is the reference design and main target board for MicroPython.
You can buy one at the store.
Target audience: Users with a pyboard.
User avatar
Mike Teachman
Posts: 155
Joined: Mon Jun 13, 2016 3:19 pm
Location: Victoria, BC, Canada

Re: Pin ID on pyboard

Post by Mike Teachman » Fri Feb 18, 2022 12:48 am

Andrew1234 wrote:
Thu Feb 17, 2022 1:21 am
I'm trying your code with a more expensive encoder that claims 600 P/R
Can you provide a specification reference to the encoder? I am just interested to learn more about the encoder hardware that people are using. Also, how are you using the encoder? e.g. manual turning? other?

As OutoftheBOTS indicated, the pulse rate of this encoder may exceed the capabilities of the MicroPython scheduler-based interrupt handling. pythoncoder's FAQ is definitely a must-read.

Please followup and let us know what you learn. Thanks!

OutoftheBOTS_
Posts: 847
Joined: Mon Nov 20, 2017 10:18 am

Re: Pin ID on pyboard

Post by OutoftheBOTS_ » Fri Feb 18, 2022 8:52 am

Although we call then timers whats a timer really is is a hardware counter. It can count up or down and it will count 1 step every time the input clock ticks. These timers can have input clocks in the MHz.

How the timer can be used to count encoders is that 2 encoder pins go through some logic circuits to create both the input clock signal every time there is a change in the pin states and depending on the order of the changing of the states it sets whether it counts up or down.

The documents for the hardware encoder counters says it has a hardware filter to deal with jitter.

I have never had a problem with jitter. The little hand dial encoder counters will have jitter as they have contacts but all the optical encoders and magnetic encoders that I have used have had hardware filters on them and when I out them on my scope they are always jitter free.

OutoftheBOTS_
Posts: 847
Joined: Mon Nov 20, 2017 10:18 am

Re: Pin ID on pyboard

Post by OutoftheBOTS_ » Fri Feb 18, 2022 8:55 am

OutoftheBOTS_ wrote:
Fri Feb 18, 2022 8:52 am
Although we call then timers whats a timer really is is a hardware counter. It can count up or down and it will count 1 step every time the input clock ticks. These timers can have input clocks in the MHz.

How the timer can be used to count encoders is that 2 encoder pins go through some logic circuits to create both the input clock signal every time there is a change in the pin states and depending on the order of the changing of the states it sets whether it counts up or down.

The documents for the hardware encoder counters for STM32 says it has a hardware filter to deal with jitter.

I have never had a problem with jitter. The little hand dial encoder counters will have jitter as they have contacts but all the optical encoders and magnetic encoders that I have used have had hardware filters on them and when I put them on my scope they are always jitter free.

Andrew1234
Posts: 20
Joined: Sat Oct 16, 2021 3:58 pm

Re: Pin ID on pyboard

Post by Andrew1234 » Fri Feb 18, 2022 1:06 pm

Can you provide a specification reference to the encoder? I am just interested to learn more about the encoder hardware that people are using. Also, how are you using the encoder? e.g. manual turning? other?

I'm experimenting with a Taiss incremental rotary encoder with 600 pulses per revolution. It costs about $18 on Amazon. My use case is a frequency tuning control for a HF radio (so manual tuning). The bands are generally 300kHz to 500kHz, so there is a challenge to find the right balance between resolution and a reasonable number of turns to traverse the band. I haven't settled on the answer yet. But if we assume 10Hz per pulse, it's clear that 25 P/R is too small. At 600 P/R, 10Hz resolution will mean 6kHz per revolution. To traverse the band quickly, it's possible to spin the encoder. I've also seen that some people detect the rate that the encoder is turning, and adjust the frequency per pulse accordingly. So for example if it is detected that the knob has been spun, each pulse becomes say 100Hz. Of course detecting the rate of pulses increases the complexity, but it's probably necessary to have the right usability. I guess it's not so easy to replicate the analog tuning solutions of the vintage radios!

Regards
Andy

dshriver
Posts: 3
Joined: Mon Jul 19, 2021 10:17 am

Re: Pin ID on pyboard

Post by dshriver » Fri Feb 18, 2022 6:18 pm

Have used a variety of encoders from multiple vendors for motor position counting, but have standardized on encoders from USDigital in last couple of years. Have successfully used their E5, E4T & E8T optical kit encoders. Personally prefer encoders, like the E5 with index support. Recently purchased their E16 (toothpick size - e.g 1.5mm to 2mm shaft support) & M3K (magnetic) encoders but have not tested yet. Apparently the M3K optionally can report on absolute position via 12 bit PWM which sounds nice, once I figure out how to use (i.e. how to convert the PWM range to 0 to 360 degrees). Oh well - you asked.

rkompass
Posts: 66
Joined: Fri Sep 17, 2021 8:25 pm

Re: Pin ID on pyboard

Post by rkompass » Fri Feb 18, 2022 11:49 pm

Hello

As I understand it, incremental encoder (knobs) are better handled by transition state machines, because these handle bouncing in one of the two X, Y signals only well. So for this use case I would recommend Mike Teachman s solution or the code below.
Interrupt routines like those in @pythoncoder s Encoder class https://github.com/peterhinch/micropyth ... NCODERS.md are prone to error here, because they do not take the last readout signals into account: A change in YX from 00 to 01 triggering an interrupt will lead to an error if X bounced back before the interrupt routine reads the X value, for example. The error consists in then decrementing position. I found this to be the case in a simple setup with two switches pressed by hand.
A compromise which seems to work well, is to debounce each signal independent from the other. I changed pythoncoders class a bit:

Code: Select all

from machine import Pin

class Encoder:
    def __init__(self, pin_x, pin_y, scale=1):
        self.scale = scale
        self.pin_x = pin_x
        self.pin_y = pin_y
        self._pos = 0
        self.x_old = pin_x()
        self.y_old = pin_y()
        try:
            self.x_int = pin_x.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=self.x_callb, hard=True)
            self.y_int = pin_y.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=self.y_callb, hard=True)
        except TypeError:
            self.x_int = pin_x.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=self.x_callb)
            self.y_int = pin_y.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=self.y_callb)
            
    def x_callb(self, pin):
        if pin() ^ self.x_old:
            self._pos += -1 if self.x_old ^ self.pin_y() else 1
            self.x_old ^= 1

    def y_callb(self, pin):
        if pin() ^ self.y_old:
            self._pos += 1 if self.y_old ^ self.pin_x() else -1
            self.y_old ^= 1

    def position(self, value=None):
        if value is not None:
            self._pos = round(value / self.scale)
        return self._pos * self.scale

class Report:
    def __init__(self, timer, encoder):
        self.position = encoder.position
        timer.callback(self.cb)

    def cb(self, tim):
        print(self.position())

px = Pin('X11', Pin.IN, Pin.PULL_UP)
py = Pin('X12', Pin.IN, Pin.PULL_UP)

enc = Encoder(px, py)
rep = Report(pyb.Timer(4, freq=1), enc)
The

Code: Select all

if pin() ^ self.x_old: 
together with

Code: Select all

self.x_old ^= 1
does this.

Rotary encoders on motors on the other hand usually do not have contact bounce, as stated here before, so only the speed of processing the signals counts. If processing in hardware is not possible and processing in C not available I suggest evaluating only the change in one of the two signals, say X. Unfortunately we cannot register two hard interrupt routines for rising and falling edge of the same signal in micropython (this would make the code simpler, tried it just now with the pyb and machine module). Why this limitation, btw.?
So, for speed leave out the y interrupts, y callback routine and self.y_old and wrapping the callback routine in @micropython.viper
Also the callback could be adapted to work without unnecessary arguments, referring to fixed pins and global variables for the position to make it faster. Trying this I arrived at:

Code: Select all

import micropython
import stm
from machine import Pin

micropython.alloc_emergency_exception_buf(160)

#  ---- these lines are for finding the constants 1073874960, 4 and 5 below ---
px = Pin('X11', Pin.IN, Pin.PULL_UP)
py = Pin('X12', Pin.IN, Pin.PULL_UP)
_ports = [stm.GPIOA, stm.GPIOB, stm.GPIOC]
print('px addr, bit: ', _ports[px.port()] + stm.GPIO_IDR, px.pin())
print('py addr, bit: ', _ports[py.port()] + stm.GPIO_IDR, py.pin())
# -----------------------------------------------------------------------------
_pos : int = 0
_xold : int = 0

@micropython.viper
def enc_callb(line):
    global _pos, _xold
    gpio_xy = ptr32(1073874960)
    pos : int = int(_pos)
    xold : int = int(_xold)
    xval : int = (gpio_xy[0] >> 4) & 1
    yval : int = (gpio_xy[0] >> 5) & 1
    if xval ^ xold:
        pos += 1 if xold ^ yval else -1
        xold ^= 1
        _pos = pos
        _xold = xold

class Report:
    def __init__(self, timer):
        timer.callback(self.cb)

    def cb(self, tim):
        print(_pos)


px.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=enc_callb, hard=True)

rep = Report(pyb.Timer(4, freq=1))

OutoftheBOTS_
Posts: 847
Joined: Mon Nov 20, 2017 10:18 am

Re: Pin ID on pyboard

Post by OutoftheBOTS_ » Sat Feb 19, 2022 9:29 pm

Andrew1234 wrote:
Fri Feb 18, 2022 1:06 pm
Can you provide a specification reference to the encoder? I am just interested to learn more about the encoder hardware that people are using. Also, how are you using the encoder? e.g. manual turning? other?

I'm experimenting with a Taiss incremental rotary encoder with 600 pulses per revolution. It costs about $18 on Amazon. My use case is a frequency tuning control for a HF radio (so manual tuning). The bands are generally 300kHz to 500kHz, so there is a challenge to find the right balance between resolution and a reasonable number of turns to traverse the band. I haven't settled on the answer yet. But if we assume 10Hz per pulse, it's clear that 25 P/R is too small. At 600 P/R, 10Hz resolution will mean 6kHz per revolution. To traverse the band quickly, it's possible to spin the encoder. I've also seen that some people detect the rate that the encoder is turning, and adjust the frequency per pulse accordingly. So for example if it is detected that the knob has been spun, each pulse becomes say 100Hz. Of course detecting the rate of pulses increases the complexity, but it's probably necessary to have the right usability. I guess it's not so easy to replicate the analog tuning solutions of the vintage radios!

Regards
Andy
Since your turning by hand your not going to be getting a crazy high tick freq like a motor encoder and also your case use wouldn't matter if some ticks got missed being counted so a software encoder counter will do fine.

Knowing how your encoder works whether it has jitter from contacts or it is a very clean signal also will govern your choice. If you have a scope then take a zoomed in close look at the transitions from low to high and see if it has switch bounce or if it is really clean transition.

May I also suggest that for your use case that you have a button that changes how much freq it adjust changes when the button is pressed. You can have the button is like a fast adjust mode that when the button is pressed that it jumps a big change in freq then when the button isn't pressed it jumps a small amount. That way you press the button and get close to the freq u want then let go of the button and fine tune the freq

Post Reply