Timers (Solved)

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.
adamboho
Posts: 15
Joined: Fri Oct 10, 2014 4:27 pm

Timers (Solved)

Post by adamboho » Sun Oct 19, 2014 11:38 pm

Can someone tell me what's wrong with this code, only one timer is working!

Code: Select all

import pyb


test = pyb.Timer(1, freq=1000)
test_2 = pyb.Timer(2, freq=500)







def servo1():
    pyb.Pin('X1', pyb.Pin.OUT_PP).high()# Pin X1 on
    pyb.delay(1)
    pyb.Pin('X1', pyb.Pin.OUT_PP).low()# Pin X1 off



def servo2():
    pyb.Pin('X2', pyb.Pin.OUT_PP).high()# Pin X1 on
    pyb.delay(1)
    pyb.Pin('X2', pyb.Pin.OUT_PP).low()# Pin X1 off





while True:
    test.callback(lambda t:servo1())
    test_2.callback(lambda t:servo2())





pyb.LED(4).on()
Last edited by adamboho on Tue Oct 21, 2014 10:20 pm, edited 1 time in total.

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

Re: Timers

Post by pythoncoder » Mon Oct 20, 2014 7:34 am

You seem to have a 1mS delay in a callback which is called at intervals of 1mS. I'm unsure of the likely consequences of this (re-entrancy?) but I suspect they aren't good.

Regards, Pete
Peter Hinch
Index to my micropython libraries.

adamboho
Posts: 15
Joined: Fri Oct 10, 2014 4:27 pm

Re: Timers

Post by adamboho » Mon Oct 20, 2014 2:19 pm

The thing is, that this two timers not working at the same time. If I disable Timer 1 then the Timer 2 working, if i disable Timer 2 then the Timer 1 working. When both Timers are on then only Timer 1 is working. What a hell is going on?

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

Re: Timers

Post by dhylands » Mon Oct 20, 2014 5:57 pm

What's happening is this:

1 - You start Timer 1 and ask it to generate an interrupt once every millisecond
2 - You wrote an interrupt callback routine which consumes 100% of the CPU for just over a millisecond

Timers all run at equal priority (in the IRQ priority sense), but lower numbered timers will have their callbacks processed before higher numbered timers.

As soon as your Timer1 callback has finished running, and interrupts are re-enabled, the timer1 interrupt handler fires again. So the Timer2 callback never runs.

If you're trying to control RC-style servos, you'd be much better off to use PWM.

Timers 1 and 8-11 run at 168 MHz
The remaining timers run at 84 MHz

I notice you're using X1 and X2, which happen to be dealt with already by the Servo class: http://micropython.org/doc/module/pyb/Servo Servos 1-4 map onto X1-X4

Asssuming you don't want to use the Servo class, then you could do the following:

Code: Select all

t2 = pyb.Timer(2, prescaler=83, period=19999
servo1 = t2.channel(1, pyb.Timer.PWM, pin=pyb.Pin.board.X1)
servo2 = t2.channel(2, pyb.Timer.PWM, pin=pyb.Pin.board.X2

servo1.pulse_width(1000)
servo2.pulse_width(1000)
The simplest place to see the channel mapping is to look at the generated pins_af.py file inside the build directory. I happen to have a copy checked in over here: https://github.com/dhylands/upy-example ... pins_af.py

We can see that Timer 2 has channels 1- 4 mapped onto X1 to X4.

So we've setup one timer, and setup 2 channels on that timer. The timer has a resolution of 1 microsecond (84 MHz / (83 + 1)) = 1 MHz. It cycles once every (19999 + 1) counts, or once every 20000 microseconds (20 milliseconds), which corresponds to a frequency of 50 Hz.

We can set the pulse width on each channel to be any value between 0 and 20000 in 1 microsecond increments. For RC style servos, we want values in the range of about 500 to 2500 (the nominal values are 1000-2000, with 1500 being the center value), but most servos accept values outside that range.

And it consumes 0% CPU.

As a general rule, you should NEVER put delays into interrupt callbacks. You're just asking for trouble. It should always raise a red flag, and you should always try to find an alternate solution.

adamboho
Posts: 15
Joined: Fri Oct 10, 2014 4:27 pm

Re: Timers

Post by adamboho » Mon Oct 20, 2014 6:57 pm

Yes, You right, but I tried everything and I can't find how to generate pwm to control my stepper motor. I've got this stepper driverhttp://www.gbeshop.com/InfoBase/downloa ... ual-EN.pdf. In future I want three of them, each at different pin and speed. With Your code and many others, the motor runs same speed on both pins, pulse_width() don't do any thing, no matter to what number I change it. The motor change speed only when I change freq or prescaler in the timer. I have no more ideas, please help!

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

Re: Timers

Post by dhylands » Mon Oct 20, 2014 9:13 pm

ok - yeah those types of servos are different than the RC servos I was referring to.

Steppers/servo controllers (like the one you referred to) use a direction and pulse. According to the documentation you provided, the pulse width needs to be at least 1.5 usec. The frequency of the pulses is what determines the speed.

So you could either use a short delay (use something like pyb.udelay(5)) inside your callback. What you had originally was equivalent to pyb.udelay(1000)

Using the callback is probably the right thing to do for driving stepper/servos so that you can keep track of your position.

adamboho
Posts: 15
Joined: Fri Oct 10, 2014 4:27 pm

Re: Timers

Post by adamboho » Mon Oct 20, 2014 9:44 pm

Thanks dhylands. But how can I make it, to run three steppers on different pins and speed? Must be a way to do it. The thing is in the timers, just I cannot understand how to set up three different timers and change the freq when I need to. I like Python and PyBoard, I don't want to go back to Arduino :)

Maybe I should not use Timers, just udelay().

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

Re: Timers

Post by dhylands » Mon Oct 20, 2014 11:14 pm

There are a variety of ways to do it, depending on what you want to do.

I don't recommend just using pyb.udelay - you'll wind up with jitter and that can cause you to lose steps.

One method is to run a timer at a fixed frequency, and use Output Compare to generate interrupts (I haven't done this myself yet, but I'd be happy to try it out for you).

Mach 3 uses a similar technique, and allows 25kHz, 40kHz and something else IIRC. Let's pick 32768 Hz since that's a nice round computer number and allows certain types of arithmetic to work.

So you'd set the timer up to count from 0 to 32767 and then back to 0 again (or in hexadecimal 0x0000 to 0x7fff)

Then you'd setup a channel in OutputCompare mode (more specifically MODE_OC_TIMING) so we're not driving a pin.

We can then use the compare feature of the channel to generate an interrupt when the counter reaches a particular value. You can use to drive some arbitrary number of steppers.

So now you need to figure out when to generate a pulse. Normally, you'd keep track of this at some higher level of precision, and allow the error to accumulate. So let's suppose we want to generate a pulse every millisecond. That corresponds to once every 32.768 timer ticks.

Now the interrupts in micropython can't deal with floats, so we'd need to use some type of fixed point math. Lets say we pick 15.8 (15 bits with 8 bits of fraction).
To generate an interrupt every 1 msec, we'd want 32.768 ticks or 32 ticks with .768 * 256 = 196. So we keep a 23-bit integer counterm and ignore the bottom 8 bits. 32 = 0x20. 196 = 0xc4. So out fixed point incrementer is 0x20c4, and we'd want to generate an interrupt for time 0x20c4, 0x20c4 + 0x20c4 = 0x4188, and 0x4188 + 0x20c4 = 0x624c.

So we setup the compare register to look for 0x20c4 >> 8 == 0x20 the first time, and 0x4188 >> 8 == 0x41 the second time, and 0x624c >> 8 = 0x62 the third time.

With more steppers, you basically wind up with a little state machine for each stepper that figures out when the next expiry is going to be, and you take the first one.

Personally, I think that's how I would deal with it. Of course, there are lots of other ways of doing it. You could use one timer per stepper. If you decided to use one timer per stepper then you could set the timers to go off at a particular frequency and change the frequency on the fly (you would need to do this to deal with acceleration and deceleration).

Depending on the exact step rates needed, it might turn out that micropython can't keep up, in which case the core part of the pulse generator might need to be implemented in C. But I'd try to figure out where those limits are first.

adamboho
Posts: 15
Joined: Fri Oct 10, 2014 4:27 pm

Re: Timers

Post by adamboho » Tue Oct 21, 2014 7:44 pm

What about this?

Code: Select all

import pyb



switch_x_b = 1        # b for boolean
switch_y_b = 1        # b for boolean
switch_z_b = 1        # b for boolean

tim_x = pyb.Timer(1, freq=1)
tim_y = pyb.Timer(2, freq=1)
tim_z = pyb.Timer(4, freq=1)



def servo1():
    global switch_x_b
    if switch_x_b:
        pyb.Pin('X1', pyb.Pin.OUT_PP).high()
        switch_x_b = 0
    else:
        pyb.Pin('X1', pyb.Pin.OUT_PP).low()
        switch_x_b = 1



def servo2():
    global switch_y_b
    if switch_y_b:
        pyb.Pin('X2', pyb.Pin.OUT_PP).high()
        switch_y_b = 0
    else:
        pyb.Pin('X2', pyb.Pin.OUT_PP).low()
        switch_y_b = 1



def servo3():
    global switch_z_b
    if switch_z_b:
        pyb.Pin('X3', pyb.Pin.OUT_PP).high()
        switch_z_b = 0
    else:
        pyb.Pin('X3', pyb.Pin.OUT_PP).low()
        switch_z_b = 1





tim_x.callback(lambda t:servo1())
tim_x.init(freq = 500)

tim_y.callback(lambda t:servo2())
tim_y.init(freq = 1000)

tim_z.callback(lambda t:servo3())
tim_z.init(freq = 1500)



        
pyb.LED(3).on()
It works perfectly for me. What You think about it?

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

Re: Timers

Post by dhylands » Tue Oct 21, 2014 9:04 pm

Sure - that works. You're going to get pulses at half the frequency of the timer (since it takes 2 timer ticks to make a pulse).

Post Reply