ExtInt problems

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
Jonas
Posts: 29
Joined: Fri Jan 23, 2015 8:32 pm

ExtInt problems

Post by Jonas » Sun Mar 01, 2015 11:12 pm

Hi,
I'm playing a bit with interupts. Sometimes this code works but most times I get errors. Either it is 'vector is in use', or 'callback is not defined'.
If I run some other code and then change back to this it work one time, then the errors.
I have tried both soft and hard reset.
I'm just learning this and i guess it's something I'm doing wrong...cant figure out what...

Code: Select all

from pyb import Pin, ExtInt
extint = ExtInt(Pin('X22'), ExtInt.IRQ_RISING, Pin.PULL_NONE, callback)
def callback(line):
    print(line)

User avatar
dhylands
Posts: 3254
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: ExtInt problems

Post by dhylands » Sun Mar 01, 2015 11:49 pm

Jonas wrote:Hi,
I'm playing a bit with interupts. Sometimes this code works but most times I get errors. Either it is 'vector is in use', or 'callback is not defined'.
If I run some other code and then change back to this it work one time, then the errors.
I have tried both soft and hard reset.
I'm just learning this and i guess it's something I'm doing wrong...cant figure out what...

Code: Select all

from pyb import Pin, ExtInt
extint = ExtInt(Pin('X22'), ExtInt.IRQ_RISING, Pin.PULL_NONE, callback)
def callback(line):
    print(line)
try doing this instead:

Code: Select all

from pyb import Pin, ExtInt
def callback(line):
    print(line)
extint = ExtInt(Pin('X22'), ExtInt.IRQ_RISING, Pin.PULL_NONE, callback)
If you were just typing that in, then callback doesn't exist when you type the line:

Code: Select all

extint = ExtInt(Pin('X22'), ExtInt.IRQ_RISING, Pin.PULL_NONE, callback)
Similarly, once the callback is defined, if you just do:

Code: Select all

extint = ExtInt(Pin('X22'), ExtInt.IRQ_RISING, Pin.PULL_NONE, callback)
extint = ExtInt(Pin('X22'), ExtInt.IRQ_RISING, Pin.PULL_NONE, callback)
then you'll get the 'vector is in use' error the second time you do it.

You can can set the callback to None to unset the callback, so this would work:

Code: Select all

extint = ExtInt(Pin('X22'), ExtInt.IRQ_RISING, Pin.PULL_NONE, callback)
extint = ExtInt(Pin('X22'), ExtInt.IRQ_RISING, Pin.PULL_NONE, None
extint = ExtInt(Pin('X22'), ExtInt.IRQ_RISING, Pin.PULL_NONE, callback)

Jonas
Posts: 29
Joined: Fri Jan 23, 2015 8:32 pm

Re: ExtInt problems

Post by Jonas » Fri Mar 06, 2015 6:25 pm

Thank you very much, this really helped alot!

But now i got stuck with another problem...
I'm trying to make a fast quadrature rotary encoder.
I would like to read positions up to about 6000 rpm but it freeze when reaching about a 1000 rpm.
I have tried the examples I found on the forum, but they are also to slow, so I tried to make the code as minimum as possible hoping to speed things up.
Here it is:

Code: Select all

import pyb
import micropython
micropython.alloc_emergency_exception_buf(100)
x21_pin = pyb.Pin.board.X21

x22_pin = pyb.Pin.board.X22
counter1 = 0
forw = 0
def x21_callback(line):
  global forw
  if x21_pin.value() ^ x22_pin.value() == 0:
    forw = 1
  else:
    forw = 0
    
def x22_callback(line):
  global counter1
  if forw == 1:
    counter1 += 1
  if forw == 0:
    counter1 -=1

extintX1 = pyb.ExtInt(x21_pin, pyb.ExtInt.IRQ_RISING_FALLING, pyb.Pin.PULL_UP, x21_callback)
extintX2 = pyb.ExtInt(x22_pin, pyb.ExtInt.IRQ_RISING_FALLING, pyb.Pin.PULL_UP, x22_callback)
while True:
  
  print(counter1)
I use one line for direction and the other for pulses, it's a 1024 P/R so I get 2048 pulses per revolution.
I bet the pyboard is fast enough, so it must be my code.
Does anyone know a way to speed it up?

Jonas

User avatar
dhylands
Posts: 3254
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: ExtInt problems

Post by dhylands » Fri Mar 06, 2015 8:34 pm

Hmm.

2048 P/R means 8192 edges per rev, and 6000 RPM = 100 revs/sec

So you're trying to process about 819200 pulses/second which is a bit on the fast side for uPy I think. You can try using one of the inline assembler modes, but that still seems pretty high.

The Timers actually have a mode to do quadrature decoding directly in hardware, but it hasn't been exposed yet in micropython.

Jonas
Posts: 29
Joined: Fri Jan 23, 2015 8:32 pm

Re: ExtInt problems

Post by Jonas » Fri Mar 06, 2015 9:45 pm

Sorry, I think I didn't explain that right. It's 1024 pulses per rev for one edge. I'm only using two edges for position, thats 2048 edges per rev, plus two more edges every time the direction changes, as I understand it.
It might still be to much.

6000 rpm is absolute max, but I need to run at 3000 rpm and not missing a single pulse.
This is the first steps for a closed loop from my stepper motors on my cnc-mill.
I would like the cnc-program(mach3) to send the stepper pulses to the pyboard and then let pyboard send them to the motor drives. The rotary encoders value will be compared to the motor pulses, checking them of one by one, and making sure the position is really reached. Sort of a servo-stepper motor hybrid.
I have absolutely no idea how to do all this, but I like to have real goals to aim for while learning python3 and micropython.
Maby in a few years...

blmorris
Posts: 348
Joined: Fri May 02, 2014 3:43 pm
Location: Massachusetts, USA

Re: ExtInt problems

Post by blmorris » Fri Mar 06, 2015 10:38 pm

If you are looking to read encoders for each axis of a 3-axis milling machine, that may be even more reason to look at using the encoder modes of the timer hardware. Then you can run as fast as you want without worrying about CPU cycles.
As @dhylands pointed out, this isn't exposed by the current timer class, but with access to the control registers really everything is available even if it isn't actually easy.
I'm not promising anything, but I took a quick look at the timer section of the programmer's reference manual; I think that enabling the encoder mode isn't much more complicated than the external counter mode I just did here.

User avatar
dhylands
Posts: 3254
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: ExtInt problems

Post by dhylands » Sat Mar 07, 2015 1:10 am

So the way that quadrature encoders normally works is that there are 2 channels called A and B.

One pulse is typically a rise and fall on the A channel, with a corresponding rise and fall on the B channel that's 90 degrees out of phase.

Do your a data sheet for the encoder?

The step/dir signals are normally output to the stepper driver.

Sounds like an interesting project. I was actually thinking about looking into adding support for generating stepper pulses.

User avatar
pythoncoder
Posts: 3850
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: ExtInt problems

Post by pythoncoder » Sat Mar 07, 2015 8:06 am

@dhylands It sounds like it's a 1024 pulses per rev encoder, which is 4096 edges per rev giving 409,600 interrupts per second rather than 819,200. Still very high indeed, especially if there are three of them!

@Jonas If you're after absolute accuracy from a quadrature encoder, there are some subtleties which can bite you. On a real machine there can be vibration. If a machine stops with an encoder poised precisely at the position of emitting an edge you can get jitter around an edge which can lead to arbitrarily short pulses: there can be issues with occasional missed pulses even on hardware decoders. I've no idea whether software based solutions on the MicroPython hardware can handle this - a lot of testing would be required - but I think it's another argument for trying to access the built in hardware support. The chip designers have hopefully addressed these issues.

As a general point on software solutions you can achieve double the resolution that your solution manages with code along the following lines. I don't claim it solves the "absolute accuracy" issue, though.

Code: Select all

class Encoder(object):
    def __init__(self, pin_x, pin_y, reverse, scale):
        self.reverse = reverse
        self.scale = scale
        self.forward = True
        self.pin_x = pin_x
        self.pin_y = pin_y
        self._pos = 0
        self.x_interrupt = pyb.ExtInt(pin_x, pyb.ExtInt.IRQ_RISING_FALLING, pyb.Pin.PULL_NONE, self.x_callback)
        self.y_interrupt = pyb.ExtInt(pin_y, pyb.ExtInt.IRQ_RISING_FALLING, pyb.Pin.PULL_NONE, self.y_callback)

    def x_callback(self, line):
        self.forward = self.pin_x.value() ^ self.pin_y.value() ^ self.reverse
        self._pos += 1 if self.forward else -1

    def y_callback(self, line):
        self.forward = self.pin_x.value() ^ self.pin_y.value() ^ self.reverse ^ 1
        self._pos += 1 if self.forward else -1

    @property
    def position(self):
        return self._pos*self.scale

    def reset(self):
        self._pos = 0
Peter Hinch

Jonas
Posts: 29
Joined: Fri Jan 23, 2015 8:32 pm

Re: ExtInt problems

Post by Jonas » Sat Mar 07, 2015 9:45 am

Thanks for your input!

Heres the datasheet: http://dlnmh9ip6v2uc.cloudfront.net/dat ... coders.pdf

It's the incremental one i have. Not to bad quality for china made encoders. I think sparkfun sell these.
I'm using one channel for direction and the other for position. Not sure its the best way, but since I will later compare the encoder output with the cnc-programs G-code output, it might be easier to separate the two from start.
The cnc-program first outputs a high or low for direction on one pin and then starts the pulses on a second pin.
I need to somehow save or buffer the g-code output on the pyboard in two files or buffers, one for clockwise and one for counter clockwise.
Thats why I thought of using one encoder channel for direction and the other for position.
The stepper motors have 200 steps/rev, but I can also make them run at halfstep, quarterstep and so on to increase precision. That means I can't compare the signals directly, I must scale the encoders output to a distance, a number of pulses for each millimeter.

I used @pythoncoder's code alot as a reference,(thanks alot for that), otherwise I had no idea where to begin. When it froze on higher rpm's I tried to scale it down to a minimum, get rid of classes and other things I thought might slow it down. But the result was the same.
A few missing edges is not to much of a problem, but the aim has to be that no pulse will be missed. I dont have any problems as I have it now with the stepper motors. It can run for hours and still spit out parts well within the given tolerance. But stepper motors never really know where they are and no alarm is raised if things goes wrong. And its always possible to call G28 to return to reference position and reset.

The hardware acces is way over my head for now. I can only hope someone else gets to it and wright an understandable tutorial :)

User avatar
dhylands
Posts: 3254
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: ExtInt problems

Post by dhylands » Sat Mar 07, 2015 7:31 pm

Jonas wrote:Thanks for your input!

Heres the datasheet: http://dlnmh9ip6v2uc.cloudfront.net/dat ... coders.pdf

It's the incremental one i have. Not to bad quality for china made encoders. I think sparkfun sell these.
The sparkfun ones are these ones: https://www.sparkfun.com/products/11102 which link to the same data sheet. Those ones output whats known as a gray code, which is what I was describing earlier. They don't output step/dir.

Here's some background on how those work: http://www.robotshop.com/media/files/pd ... rs011a.pdf
You can use a simple state machine to do it in SW. Or you can use the Timer encoder (not coded yet - but I'll see what I can do), or you can use an eternal device like: the LS7366 http://www.lsicsi.com/pdfs/LS7366.pdf
Jonas wrote:I'm using one channel for direction and the other for position. Not sure its the best way, but since I will later compare the encoder output with the cnc-programs G-code output, it might be easier to separate the two from start.
The cnc-program first outputs a high or low for direction on one pin and then starts the pulses on a second pin.
I need to somehow save or buffer the g-code output on the pyboard in two files or buffers, one for clockwise and one for counter clockwise.
Thats why I thought of using one encoder channel for direction and the other for position.
So lets take a step back. On the output side, you're trying to generate step/dir for a servo/stepper and monitor using quadrature with your encoder.
On the input side, are you taking gcode in? Or step/dir signals?

I've been toying with the idea of creating a gcode to step/dir device. The one I'll be looking at initially is the grbl source code: https://github.com/grbl/grbl which was designed to run on an ATMega328.
Jonas wrote:The stepper motors have 200 steps/rev, but I can also make them run at halfstep, quarterstep and so on to increase precision. That means I can't compare the signals directly, I must scale the encoders output to a distance, a number of pulses for each millimeter.
Generally speaking you can only increase precision using 1/2 steps and 1/4 steps. Anything beyond that is really about noise reduction and resonance elimination and you can't reliably increase precision.

I think adding quadrature support to the Timer class would be a good place to start, so that's probably what I'll focus on right now.

Post Reply