Run assembly function within state machine

RP2040 based microcontroller boards running MicroPython.
Target audience: MicroPython users with an RP2040 boards.
This does not include conventional Linux-based Raspberry Pi boards.
Post Reply
Kostrinker
Posts: 4
Joined: Tue Aug 10, 2021 10:05 am

Run assembly function within state machine

Post by Kostrinker » Fri Aug 13, 2021 10:26 am

Hello everyone,

I wrote a function in assembly with the @asm_thumb decorator which allows me to trigger the ADC register for a single conversion, store its value in a register (lets say r0), trigger some IOs, trigger again the ADC and store this second value in r1. Then the function return the difference between r1 and r0 and store the value in the memory adress of a predefined variable.
I chose assembly since I needed to do these operations as fast as possible (and it was new to me, so also a bit for the fun).

Right now the function works well in a while loop on the CPU but therefore cannot use it for other (more interesting?) tasks.

So I thought that I could set up a state machine, with only one irq and use my assembly function as the irq_handler.

Code: Select all

@micropython.asm_thumb #My function in inline assembly 
def my_assembly_fun(r0):
	...do stuff with ADC and PIO register...
	return r0
	
def irq_handler(x): #The function that will be called when the irq is triggered
    my_assembly_fun()

@rp2.asm_pio() #The PIO function that only call an IRQ and wait 31 cycles
def pio_function():
    wrap_target()
    irq(0)  [31]      
    wrap()

rp2.PIO(0).irq(lambda gpio:irq_handler(gpio))  #Setting the function for the handling of the IRQ
sm = rp2.StateMachine(0, pio_function, freq=100000) #The initialization of the statemachine

Even if this work (with jitter) it doesn't seem correct.

What would you advice to do?
For what I understood (hopefully wrong), with in the PIO assembly language I cannot "peek" or "poke" registers and therefore trigger and sample my ADC register.

Thanks for your help,
Fede

hippy
Posts: 130
Joined: Sat Feb 20, 2021 2:46 pm
Location: UK

Re: Run assembly function within state machine

Post by hippy » Mon Aug 16, 2021 3:05 pm

Kostrinker wrote:
Fri Aug 13, 2021 10:26 am
Even if this work (with jitter) it doesn't seem correct.
In what way does it not seem correct ?

The problem I can envisage with a PIO routine which generates an interrupt every 32 cycles (every 32us), is that may be far faster than the MicroPython side of things can keep up with, though I haven't tested it.
Kostrinker wrote:
Fri Aug 13, 2021 10:26 am
For what I understood (hopefully wrong), with in the PIO assembly language I cannot "peek" or "poke" registers and therefore trigger and sample my ADC register.
No, you can't do any of that from within a PIO program.

Kostrinker
Posts: 4
Joined: Tue Aug 10, 2021 10:05 am

Re: Run assembly function within state machine

Post by Kostrinker » Thu Aug 19, 2021 11:51 am

Hello,

Thanks for your reply hippy, it helped me understand where I should focus to develop my code.
hippy wrote:
Mon Aug 16, 2021 3:05 pm
In what way does it not seem correct ?
I thought it was not correct since the state machine was not waiting the end of the irq handling's function before continuing with its script.

After reading the manual I understood that not waiting for the competition of the irq handling's function is the default, and that can be changed by writing 1 in the 5 bits (WAIT) of the IRQ instruction.

So to try to learn how the irq command works I wrote a dummy PIO program to see what happen when I change some values.
First I do my baby steps by blinking the onboard LED:

Code: Select all

@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def gpio_test(): 
    wrap_target()
    set(pins, 0b00000001)  [31]
    nop()       [31]
    set(pins, 0b00000010)  [31]
    nop()       [31]
    wrap()

sm = rp2.StateMachine(0, irq_test, freq=20000, set_base=Pin((25))

sm.active(1)

Then I try to set up an IRQ on index 0 that should WAIT.

Code: Select all

@rp2.asm_pio(set_init=(rp2.PIO.OUT_LOW,)*2)
def irq_test():
    wrap_target()
    set(000, 0b00000001)  [31]
    nop()       [31]
    set(000, 0b00000010)  [31]
    nop()       [31]
    irq(0b00100000)
    
And this make the LED blink and then wait as expected.
Meanwhile it doesn't, when I write

Code: Select all

....
irq(1,0)
....

Shouldn't irq(0b00100000) and irq(1,0) be the same syntax or am I missing someting?


Thanks!

Fede

hippy
Posts: 130
Joined: Sat Feb 20, 2021 2:46 pm
Location: UK

Re: Run assembly function within state machine

Post by hippy » Fri Aug 20, 2021 6:15 pm

Kostrinker wrote:
Thu Aug 19, 2021 11:51 am
Shouldn't irq(0b00100000) and irq(1,0) be the same syntax or am I missing someting?
The instruction assembler for 'irq' found in './modules/rp2.py' is -

Code: Select all

    def irq(self, mod, index=None):
        if index is None:
            index = mod
            mod = 0  # no modifiers
        return self.word(0xC000 | (mod & 0x60) | index)
So you would need 'irq(0x20, 0)'. This I believe is because the 'mod' part is expected to be a named constant, 'irq(block, 0)'

hippy
Posts: 130
Joined: Sat Feb 20, 2021 2:46 pm
Location: UK

Re: Run assembly function within state machine

Post by hippy » Fri Aug 20, 2021 6:40 pm

This works for me. At least so far as my "def Irq(sm)" routine is being invoked every second but the LED is not behaving as expected, which I expected to be a short flash every second. I am afraid I don't know enough about using the PIO 'irq' instruction to know if I've got that wrong, or am simply misunderstanding what it should do.

Code: Select all

from   machine import Pin
from   rp2     import asm_pio, PIO, StateMachine
import time

led = Pin(25, Pin.OUT)

@asm_pio(set_init=PIO.OUT_LOW)
def LedFlash():
    set(pins, 1) [31]
    nop()        [31]
    nop()        [31]
    set(pins, 0)
    irq(block, 0)

def Irq(sm):
    print("IRQ {}".format(time.ticks_ms() / 1000))
    time.sleep(1)

print("PIO Code ...")
for pc in range(len(LedFlash[0])):
    print("  {:02} : {:04x}".format(pc, LedFlash[0][pc]))
print("")

sm = StateMachine(0, LedFlash, freq=2000, set_base=led)
sm.irq(Irq)
sm.active(1)

while True:
    time.sleep(10)

Kostrinker
Posts: 4
Joined: Tue Aug 10, 2021 10:05 am

Re: Run assembly function within state machine

Post by Kostrinker » Sun Aug 22, 2021 10:35 am

Thanks hippy for your reply. I am now studying how to achieve that in c/c++ and see I can get more in-depth of how the IRQ is working and then see if I can bring that back in upython.

hippy
Posts: 130
Joined: Sat Feb 20, 2021 2:46 pm
Location: UK

Re: Run assembly function within state machine

Post by hippy » Mon Aug 23, 2021 6:01 pm

Having looked at the RP2040 datasheet, it states "Wait: if 1, halt until the raised flag is lowered again, e.g. if a system interrupt handler has acknowledged the flag", so the "block" equivalent of the "wait" bit on MicroPython should be halting the PIO program until the interrupt handler has acknowledged the interrupt.

That the PIO program continues suggests that mechanism is either not working (unlikely IMO) or it is working but not how anticipated.

I haven't confirmed it but I suspect the issue is the MicroPython "def Irq(sm)" or whatever method isn't actually the interrupt handler, and it is whatever sees the interrupt within MicroPython, schedules execution of that method, which is. And I suspect that's clearing the interrupt flag and allowing the PIO code to continue once the user's method is scheduled, before it is subsequently called.

If that is the case there needs to be some means to defer the clearing of the interrupt flag until after the user's method has completed execution.

Alternatively the user's method has to be made responsible for clearing the interrupt flag at its end, and needs a means to do that, I am guessing an sm.exec("irq(clear,0)") would do

The other alternative is to fire the 'irq', blocking/waiting or not, and then wait on a pull(block) with the user's method doing an sm.put() to let the PIO code continue at its end. That solution worked for me.

Or put the PIO code into a tight idling loop after the 'irq' and then have the user's method restart the state machine when it ends. That solution also worked for me.

Post Reply