Calling a TimerChannel method in Timer callback

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.
Post Reply
Iyassou
Posts: 42
Joined: Sun Jun 26, 2016 9:15 am

Calling a TimerChannel method in Timer callback

Post by Iyassou » Sat Jan 11, 2020 6:22 pm

Hello.

I'm currently writing some code that will let me control an incrementally encoded DC motor using the PID algorithm and a Timer. The error that's being fed to the algorithm is the difference between the chosen target position and the incremental encoder's count. The encoder's count is updated in the background by means of two pins X3 and X4 and a callback applied to X3 using machine.Pin.irq.
I want to use the Timer so moving the motor can be a non-blocking action.

I had the motor setup with an L298N and an external power source and was able to rotate it one way or the other at fixed speeds appropriately by setting up two channels on Timer(5) for pins X1 and X2 and using their TimerChannel.pulse_width_percent method. The encoder's count was being correctly updated.

I've also been able to implement the PID algorithm as a Timer callback by writing it in inline assembly (which was the recommended workaround for floating-point arithmetic in a callback) and it seems to be working fine. The algorithm does try to match the 'fake' data I input.

I wanted to merge these two together and have written code that makes use of a single Timer and one callback which calls the PID update assembly function and then calls the TimerChannel.pulse_width_percent methods appropriately with the newly computed percentage.

The percentage is being computed correctly (between 0 and 100 as required) however the motor isn't responding to the TimerChannel.pulse_width_percent commands at all, so I wanted to ask:

what are the effects of calling TimerChannel.pulse_width_percent within a timer callback?


Thanks.

Iyassou
Posts: 42
Joined: Sun Jun 26, 2016 9:15 am

Re: Calling a TimerChannel method in Timer callback

Post by Iyassou » Sat Jan 11, 2020 7:19 pm

I wrote some simple code to test this out:

Code: Select all

#from micropython import alloc_emergency_exception_buf
#alloc_emergency_exception_buf(100)
from pyb import Timer, Pin, LED

PPR = 2688
#red = LED(1)
timer = Timer(5)
A = timer.channel(1, Timer.PWM, pin=Pin('X1'))
B = timer.channel(2, Timer.PWM, pin=Pin('X2'))
#timer.callback(lambda tim: red.toggle())
timer.init(freq=25)

def move(a):
    p = a*100/PPR
    pos = p > 0
    p = min(max(round(abs(p)), 0), 100)
    if not p:
        A.pulse_width(0)
        B.pulse_width(0)
    elif pos:
        A.pulse_width_percent(p)
        B.pulse_width(0)
    else:
        A.pulse_width(0)
        B.pulse_width_percent(p)
As is, calling the move function with some number makes the motor rotate (clockwise or counter-clockwise depending on the sign).

When I uncomment the commented lines the red LED immediately begins flashing quite fast, however nothing happens when I call the move method. The function executes, but the motor visibly isn't rotating anymore.

User avatar
jimmo
Posts: 2754
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia
Contact:

Re: Calling a TimerChannel method in Timer callback

Post by jimmo » Mon Jan 13, 2020 4:16 am

When I run your example code (with those two lines uncommented), I get the red LED flashing and on my scope I can see the PWM waveform on X1.

What firmware version are you using?

I did notice that if I set the callback after starting the PWM, then the duty cycle dropped to ~zero (i.e. very short narrow pulses at the PWM frequency). But then after calling pulse_width_percent again, no matter what order I did things in (until the next reset) everything worked.

One thing to watch out for in general with the pyb and machine APIs, the init() method completely re-initializes everything. So by not passing a callback to init, it should be clearing the callback set in the previous line. (I think this might be a bug that it doesn't, and it's possible that the IRQ is in a weird state).

In general though, you should be able to use pulse_width and pulse_width_percent from interrupt context. (Be a bit careful with the percent version though to avoid any use of float -- i.e. always use // for integer division).

Iyassou
Posts: 42
Joined: Sun Jun 26, 2016 9:15 am

Re: Calling a TimerChannel method in Timer callback

Post by Iyassou » Thu Jan 16, 2020 12:29 pm

When I run your example code (with those two lines uncommented), I get the red LED flashing and on my scope I can see the PWM waveform on X1.

What firmware version are you using?
Hopefully this is a firmware issue then. My Pyboard's on MicroPython v1.10.
I did notice that if I set the callback after starting the PWM, then the duty cycle dropped to ~zero (i.e. very short narrow pulses at the PWM frequency). But then after calling pulse_width_percent again, no matter what order I did things in (until the next reset) everything worked.
That's good to know.
One thing to watch out for in general with the pyb and machine APIs, the init() method completely re-initializes everything. So by not passing a callback to init, it should be clearing the callback set in the previous line. (I think this might be a bug that it doesn't, and it's possible that the IRQ is in a weird state).

In general though, you should be able to use pulse_width and pulse_width_percent from interrupt context. (Be a bit careful with the percent version though to avoid any use of float -- i.e. always use // for integer division).
I have noticed that init clears everything and do set the callback straight afterwards in my other methods, thanks for confirming this.

As for floats and pulse_width_percent, in the example code I was relying on round returning an int when you don't set the decimal places argument. I saw in the docs that pulse_width_percent expected an integer between 0 and 100 which is what I had been giving it so far. I just tried passing a float to pulse_width_percent now. Calling pulse_width and pulse_width_percent returns 0 and 0.0, so good to know that doesn't work.

Post Reply