Why is this so hard?

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Why is this so hard?

Post by dhylands » Wed Jul 29, 2015 12:40 am

I'm not that familiar with the assembler stuff, so I can't really comment.

However, if you really want 2 usec pulses on some period, you're much better off to use one of the Timers to generate the pulses.

There are some examples on this page: http://wiki.micropython.org/platforms/b ... r-Examples

ebike
Posts: 55
Joined: Thu Jul 16, 2015 9:36 pm

Re: Why is this so hard?

Post by ebike » Wed Jul 29, 2015 1:01 am

No, you don't understand. I want to generate a fixed 1..2us pulse at variable frequencies for 8 outputs. These outputs will have the pulses in sequence ... I can't do that with timers.

What is wrong with my assembly that it won't work in a timer interrupt?
(NB: .. every timer interrupt/callback I want the sequence to run ..)

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

Re: Why is this so hard?

Post by dhylands » Wed Jul 29, 2015 1:26 am

The timer callback is supposed to take a single parameter, like this example:
https://github.com/dhylands/upy-example ... eat_irq.py
(which unfortunately uses a class).
This example has ic_cb as a free function:
https://github.com/dhylands/upy-example ... ic_test.py

When dealing with interrupts, adding these lines to your python script will get more detailed information about the exception (interrupt handlers aren't allowed to allocate memory. Unfortunately, throwing an exception requires allocating memory).

Code: Select all

import micropython

micropython.alloc_emergency_exception_buf(100)
With those two lines, I now get this error:

Code: Select all

>>> import flash_int
>>> uncaught exception in Timer(4) interrupt handler
TypeError: function takes 0 positional arguments but 1 were given
With this version:

Code: Select all

from pyb import Timer, Pin
import stm
import micropython

micropython.alloc_emergency_exception_buf(100)

@micropython.asm_thumb
def flash_int(r0):
    movwt(r1, stm.GPIOC)

    # get the bit mask for PAC6
    movw(r2, 1 << 6)
    # Turn on
    strh(r2, [r1, stm.GPIO_BSRRL])
    # delay for a bit
    movwt(r4, 50)   #50 = 2.5us
    label(delay_on)
    sub(r4, r4, 1)
    cmp(r4, 0)
    bgt(delay_on)
    #turn off
    strh(r2, [r1, stm.GPIO_BSRRH])


pin = Pin('C6', Pin.OUT_PP)
tim4 = Timer(4, freq=200)
tim4.callback(flash_int)
I had to add the Pin statement to get the pin configured as an output (they're configured as an input by default), then I get this output:
asm-pulse.png
asm-pulse.png (74.84 KiB) Viewed 7429 times
1.875 usec corresponds to 315 CPU cycles (at 168 MHz), which is about 6 cycles per loop. There's 1 for the SUB, 1 for the CMP, 1 for BGT and presumably 3 for the pipeline reload for taking the branch (the ARM documents say it takes 1 to 3 depending on alignment, width of instruction, and whether the processor managed to speculate the address properly or not).

ebike
Posts: 55
Joined: Thu Jul 16, 2015 9:36 pm

Re: Why is this so hard?

Post by ebike » Wed Jul 29, 2015 2:03 am

ahh, seems I had the following missing bits:

1. The "import stm" ... shame the parser did not pop an error when that was missing ..
2. missed the "r0" in the asm function call .. e.g "flash_int(r0):" ... that was not at all apparent ..

I did have the pin setup for output push-pull earlier in my code. (which I should have shown ;) )

Thanks for that, it now works just as I had hoped .. will add the other outputs now.

Cheers,

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

Re: Why is this so hard?

Post by dhylands » Wed Jul 29, 2015 2:29 am

It seems that the import of stm isn't required. I just assumed it was. I haven't really played with the asm stuff very much.

The timer callback is documented here: http://docs.micropython.org/en/latest/l ... r.callback

The emergency buffer thing is a little more obscure, and is documented here:
http://docs.micropython.org/en/latest/l ... eption_buf

I think its on my todo list to flesh out some more information about interrupts.

ebike
Posts: 55
Joined: Thu Jul 16, 2015 9:36 pm

Re: Why is this so hard?

Post by ebike » Wed Jul 29, 2015 4:29 am

Hi,

Performance seems to be the issue now.

If I have the interrupt go as fast as 50Khz with assembly toggling two outputs (not even the 8 I want eventually) then REPL stops responding. (The output is just fine though)

Is there anything that can be done there? Change priority of and REPL interrupt?

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

Re: Why is this so hard?

Post by dhylands » Wed Jul 29, 2015 4:44 am

Can you share your code?

ebike
Posts: 55
Joined: Thu Jul 16, 2015 9:36 pm

Re: Why is this so hard?

Post by ebike » Wed Jul 29, 2015 4:52 am

ok,

Code: Select all

@micropython.asm_thumb
def pulser_int(r0):
    movwt(r1, stm.GPIOC)
    
    b(loop_entry)
    
    movw(r3,5) # one less than starting bit
    
    label(loop1)
    
    # get the bit mask for PAC6   
    movw(r2, 1)
    lsl(r2,r3)
    # Turn on
    strh(r2, [r1, stm.GPIO_BSRRL])
    # delay for a bit
    movwt(r4, 50)   #50 = 2.5us
    label(delay_on)
    sub(r4, r4, 1)
    cmp(r4, 0)
    bgt(delay_on)
    #turn off
    strh(r2, [r1, stm.GPIO_BSRRH])
    
    # loop on r3
    add(r3, r3, 1)
    label(loop_entry)
    cmp(r3, 8)	# one more than end bit required
    blt(loop1)

pin = Pin('C6', Pin.OUT_PP)
pin = Pin('C7', Pin.OUT_PP)
tim4 = Timer(4, freq=50000)
tim4.callback(pulser_int)

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

Re: Why is this so hard?

Post by dhylands » Wed Jul 29, 2015 5:38 am

I decided to add some instrumentation to the timer_irq. I used 2 GPIOs.
I turned X1 on at he beginning of timer_irq_handler and turned it off at the end.
I turned X2 on just before calling mp_call_function_1 (the C function which calls the timer callback) and turned it off just after returning.

I then ran the "corrected" flash_int.py from my previous post with a freq of 50kHz. Here's my scope capture:
screenshot.png
screenshot.png (56.68 KiB) Viewed 7418 times
What I'm seeing is that time spent in timer_irq_handler is 5.625 usec.
The time spent inside mp_call_function_1 is 4.167 usec (included in the 5.625usec) and the time that the line toggled by the python assembler routine is 1.833 usec.

This gives an upper bound of 200,000 timer IRQs/sec. This would cause 100% of CPU to be used for handling timer IRQs and the REPL will not be able to run.

What exactly are you trying to do?

To go faster than the above, you're going to have to use a different technique or code it in C.

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

Re: Why is this so hard?

Post by dhylands » Wed Jul 29, 2015 5:53 am

I ran your 2-channel code with the instrumentation and I'm seeing this:
screenshot2.png
screenshot2.png (80.95 KiB) Viewed 7417 times
The bottom 2 are where the asm routine is writing. C6 is the 3rd line, C7 is the 4th line.
However, if you look at the 2nd line (high time is 17 usec, which means that about 15 usec is being spent in your assembler routine.

Post Reply