Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add better examples with how to interact between PIO routines and the Micropython program running #52

Open
Vendelator opened this issue Sep 28, 2022 · 3 comments

Comments

@Vendelator
Copy link

I have been searching quite a lot on how to communicate back and forth between PIO and Micropython on the Pi Pico with synchronized activation between state machines and PIO blocks signaling back with interrupts etc. I came up with nothing.

I started this project: https://github.com/Vendelator/RP2040_PIO_Steppermotors
Which uses the technique i was searching for.
The best source i could find was: https://dernulleffekt.de/doku.php?id=raspberrypipico:pico_pio

My suggestion is to add an example code doing this, as i have seen a lot of people are looking for this.

I provide this example program to be added as an example in: https://github.com/raspberrypi/pico-micropython-examples

This example is working as intended, but all comments have been put there quite hasty.

# Code was written for the Pi Pico, for the Pi Pico W, use an output with an external LED to get visual feedback.

# This example takes a simpler approach to explain PIO and how it integrates with Micropython
# on the Raspberry Pi Pico.

# This is example code on how to have two pairs or state machines, who are working together, run simultaneously.
# Because they can't be called at the same time, but can run independently at the same time,
# we activate the machines one by one, and then use a Pin which they both are waiting for at the same time.
# The onboard LED will turn ON while both programs are running. When each program is completed they will write to REPL.
# Once both programs are done, user will be asked to press enter again to run the programs again.

# Program can run without connecting anything to the Pi Pico, but one suggestion is adding LED's to the outputs
# and use Pins that works for you.

# I used this code as a base for my stepper motor project:
# https://github.com/Vendelator/RP2040_PIO_Steppermotors - MIT license
# The best source for explaining PIO: https://dernulleffekt.de/doku.php?id=raspberrypipico:pico_pio

# To change how many times the pins toggle, change the values in sm_0.put() and sm_4.put().
# To change how fast the program toggles, change frequency and add/remove delay [x]
# which can be done to any isntruction in the PIO routines.
# In the example i use nop() [31], which delays for 1 + 31 cycles.

# At current setting with 20_000 Hz frequency, (1 + 1 + (32 * 65) + 1) instruction in delay(), 
# pins will toggle at approximatly 10 hZ.(10 times a second)
# This means PIO block 0 will take 10 seconds to finish and PIO block 1 will take 20 seconds.


import machine     # To be able to control GPIO Pins
import rp2         # To be able to use state machines

trigger_pin = machine.Pin(25, machine.Pin.OUT) # This pin will trigger our state machines using wait(1, gpio, 25).
pio_block_0 = False                            # True/False object used to see if the program is allowed to proceed.
pio_block_1 = False                            # - " -

@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)  # Tells our program that this is a PIO routine and to set the pin low on activation.
def pin_activator():
    pull(block)                    # Wait for FIFO to fill (put), then pull data to OSR.
    mov(x, osr)                    # Copy OSR data from to X.
    wait(1, gpio, 25)              # Does nothing until GPIO Pin 25 is set to high, this is how we synchronize activation.
    
    label("Jump_Point")            # this is a header we jump back to for counting steps.
    set(pins, 1)                   # Sets the in_base Pin high.
    jmp(not_x, "end")              # if X is 0(zero), jump to end.
    irq(5)                         # Sets IRQ 5 high, signaling.
    irq(block, 4)                  # Waiting for IRQ flag 4 to clear before proceeding.
    set(pins, 0)                   # Sets the in_base Pin low.
    jmp(x_dec, "Jump_Point")       # if x is NOT 0(zero), remove one (-1) from X and jump back to "Jump_Point", Else, proceed.
    
    label("end")                   # This is a label we can jump to if X is 0.
    irq(block, rel(0))             # Signals IRQ handler of the actual state machine and waits for handler to clear the flag.
                                    
                                   # Once the handler clears the flag, the program restarts and is blocked until new data is available.

@rp2.asm_pio()                     # Does not manipulate any hardware, it's just code. empty  ()
def delay():
    wait(1, irq, 5)                # Waiting for IRQ flag 5 from pin_activator and then clears it.
    set(y, 31)                     # Sets Y to the value 31.
    label("Delay")                 # This is a header we jump back to for adding a delay.
    nop() [31]                     # nop() does nothing for [n] instructions.
    nop() [31]                     # - " -
    jmp(y_dec, "Delay")            # If Y not 0(zero), remove one (-1) from Y and make jump to delay, Else, proceed.
    irq(clear, 4)                  # Clears IRQ flag 4, allowing step_counter() to continue

                                   # At this point, the program restarts, and begins with waiting for IRQ

def pio_0_handler(sm):             # This is our Interrupt handler for PIO block 0.
    global pio_block_0             # To be able to change our global variable.
    pio_block_0 = True             # Change variable to True to be able to exit loop.
    print(sm, "sending interrupt.", "\nPio-Block 0 done!")
                                   # sm is the statemachine calling the handler.
                                   # Here we could trigger other functions or execute code
                                   # like turning a Pin on or off.

def pio_1_handler(sm):             # This is our Interrupt handler for PIO block 1
    global pio_block_1             # To be able to change our global variable
    pio_block_1 = True             # Change variable to True to be able to exit loop.
    print(sm, "sending interrupt.", "\nPio-Block 1 done!")
                                   # sm is the statemachine calling the handler.
                                   # Here we could trigger other functions or execute code
                                   # like turning a Pin on or off.


#                        --- PIO Block 0 ---
sm_0 = rp2.StateMachine(0, pin_activator, freq=20000, set_base=machine.Pin(0))
                                  # Calls rp2 and Instantiate a statemachine
                                  # (0, ... is the statemachine number. (0-7)
                                  # , pin_activator ... is the PIO routine
                                  # , freq=2000 ... sets the frequency to 2000 Hz
                                  # , in_base=machine.Pin(0) ... sets GPIO 0 as our base pin.

sm_0.irq(pio_0_handler)           # Tells the program that any interrupt from sm_0 should activate 
                                  # the function pio_0_handler(sm):

sm_1 = rp2.StateMachine(1, delay, freq=20000)
                                  # Creates object called sm_1 and binds it to state machine 1 in PIO block 0)

#                        --- PIO Block 1 ---
sm_4 = rp2.StateMachine(4, pin_activator, freq=20000, set_base=machine.Pin(1))
                                  # Calls rp2 and Instantiate a statemachine
                                  # (4, ... is the statemachine number. (0-7)
                                  # , pin_activator ... is the PIO routine
                                  # , freq=2000 ... sets the frequency to 2000 Hz
                                  # , set_base=machine.Pin(0) ... sets GPIO 0 as our base pin.

sm_4.irq(pio_1_handler)           # Tells the program that any interrupt from sm_4 should activate 
                                  # the function pio_1_handler(sm):

sm_5 = rp2.StateMachine(5, delay, freq=20000)
                                  # Creates object called sm_1 and binds it to state machine 5 in PIO block 1)

#                        --- Starting the State machines ---
sm_0.active(1), sm_1.active(1), sm_4.active(1), sm_5.active(1)
                                  # All 4 state machines are now running
                                  # State machine 0 in PIO 0 and state machine 4 in PIO 1 are both waiting to be fed data.
                                  
sm_0.put(100)                     # We can "put" data into state machine 0 using an integer.
                                  # This number will tell pin_activator() how many times to turn on/off.
any_number = 200 
sm_4.put(any_number)              # We can also use a variable.

                                  # Both state machines are now fed, have copied this value into X
                                  # and are waiting for GPIO 25 to turn high.


#                        --- Running the program ---
while True:
    input("Press enter to execute both programs synchronous...")
    trigger_pin.value(1)
    while True:
        if pio_block_0 and pio_block_1: # Check if they are both True
            trigger_pin.value(0)
            pio_block_0 = False
            pio_block_1 = False
            break
#                        --- Explaining the program ---
                                 # while is an endless loop unless exited.
                                 # input will pause the while loop until user inputs something (Presses enter in REPL).
                                 # trigger_pin.value(1) sets GPIO 25 high and the onboard LED turns on, 
                                 # this will activate our state machines and they will start toggeling the pins.
                                 # their pins and wait for out pre determined period of time before swithcing them of
                                 # again.
                                 # The second while loop will lock us into a loop where we check if
                                 # both handlers have changed pio_block_0 and pio_block_1 to True.
@webdeb
Copy link

webdeb commented Dec 22, 2022

Also, if its actually possible, would be good to have an example of how to import a .pio file (with C-SDK) into MicroPython.

@lurch
Copy link
Contributor

lurch commented Dec 24, 2022

how to import a .pio file (with C-SDK) into MicroPython.

That's (briefly) covered in section 3.9.6. of https://datasheets.raspberrypi.com/pico/raspberry-pi-pico-python-sdk.pdf

@webdeb
Copy link

webdeb commented Dec 24, 2022

how to import a .pio file (with C-SDK) into MicroPython.

That's (briefly) covered in section 3.9.6. of https://datasheets.raspberrypi.com/pico/raspberry-pi-pico-python-sdk.pdf

Ah, got it thank you! Good thing for google keywords, that I asked :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants