hippy
Posts: 10269
Joined: Fri Sep 09, 2011 10:34 pm
Location: UK

PIO "gotcha" - "set_init"

Thu May 06, 2021 2:06 pm

Does anyone know why it is essential to use "@asm_pio(set_init=PIO.OUT_LOW)" to be able to use "set(pins, 1)" etc, even when one doesn't care about the initialisation value, when the first thing one does is set the output level ?

Without that it just doesn't work, those "set()" do nothing - I am not sure if that's a bug or a feature.

I won't say how much time I wasted figuring that out :x

horuable
Posts: 147
Joined: Sat Mar 06, 2021 12:35 am

Re: PIO "gotcha" - "set_init"

Thu May 06, 2021 2:49 pm

It seems it's just a MicroPython quirk. If you do not use set_init the pin doesn't get connected to PIO even if set_base is used in SM initialisation.

I get how you feel. I have fallen into the same trap when trying to use set with pindirs (I wasn't even trying to drive pins from SM, not with set at least!), but Pico didn't know that PIO is in control of these pins, so it didn't work.

hippy
Posts: 10269
Joined: Fri Sep 09, 2011 10:34 pm
Location: UK

Re: PIO "gotcha" - "set_init"

Thu May 06, 2021 5:26 pm

I am still mystified by the MicroPython PIO API design, why the assembler needs any information at all to build its binary image, sets-up configuration which would best be done, and should be done IMO, when StateMachine() is invoked.

Time to dig into source again I guess.

User avatar
OneMadGypsy
Posts: 326
Joined: Wed Apr 28, 2021 1:57 am
Location: New Orleans, Louisiana
Contact: Website

Re: PIO "gotcha" - "set_init"

Fri May 07, 2021 2:27 am

@hippy

It's probably a combination of the below. I'd love to be more explicit, but I can't find where setting 'set_init' changes "set" value from None.

rp2.py

Code: Select all

class PIOASMEmit:
    def __init__(
        self,
        *,
        out_init=None,
        set_init=None, #<---
        #...
    }

Code: Select all

_pio_funcs = {
    #...
    "set": None, #<---
}

Code: Select all

self.prog = [array("H"), -1, -1, execctrl, shiftctrl, out_init, set_init, sideset_init] #set_init is None here
"Focus is a matter of deciding what things you're not going to do." ~ John Carmack

horuable
Posts: 147
Joined: Sat Mar 06, 2021 12:35 am

Re: PIO "gotcha" - "set_init"

Fri May 07, 2021 9:52 am

hippy wrote: I am still mystified by the MicroPython PIO API design, why the assembler needs any information at all to build its binary image, sets-up configuration which would best be done, and should be done IMO, when StateMachine() is invoked.
I think it's done this way to make sure the pins are in a continuous range, so set_init defines only the number and initial state of pins, and set_base tells where the range starts. This way you can start exactly the same state machine with different pins and don't have to bother with defining multiple pins in StateMachine() every time and the underlying code doesn't have to check if the pins you have supplied are valid.
OneMadGypsy wrote: I can't find where setting 'set_init' changes "set" value from None.
It doesn't and shouldn't. set_init tells Pico to connect pins (with the help of set_base) to PIO, so they can be driven by state machine. set is just a PIO instruction that can be used to control pins but can also do other stuff (in which case set_init and set_base don't have to be defined).

If you want to see how pins are set up during SM initialisation I think you should take a look here: https://github.com/micropython/micropyt ... pio.c#L153. I might be wrong though, C is not really my thing.

hippy
Posts: 10269
Joined: Fri Sep 09, 2011 10:34 pm
Location: UK

Re: PIO "gotcha" - "set_init"

Fri May 07, 2021 12:22 pm

OneMadGypsy wrote:
Fri May 07, 2021 2:27 am
It's probably a combination of the below. I'd love to be more explicit, but I can't find where setting 'set_init' changes "set" value from None.
I believe the internal 'set_init' is automatically set when "@asm_pio(set_init=...)" is invoked, the assembler then simply passing that on as part of the wrapped binary image rather than using it. That's part of why I don't understand why it needs specifying there and not in the StateMachine invocation, or even derived from 'set_base=' so not even needed.

Similarly for FIFO joining etc which must be specified for the '@asm_pio' and not 'StateMachine'.

That division of configuration seems artificial, unnecessary and arbitrary to me. I much prefer the way CircuitPython handles things, which is similar to the way I am doing things in my own library as below, toggling the LED every second ...

Code: Select all

ToggleGpio = """
      OSR = Pull()
      X = OSR
Wrap: Push(ISR)       # LED on
      PINS = 1
      Y = X
      Wait Y--
      Push(ISR)       # LED off
      PINS = 0
      Y = X
      Wait Y--  
"""
sm = StateMachine(ToggleGpio, pins=25)
sm.put(125_000_000)
sm.active(1)
while sm.get():
  print("Tick {}".format(time.ticks_ms / 1000))
The only thing the PIO assembler needs to do is return the binary image, where any wrap target is, the maximum number of side-set pins used and allowed, flags for which pin bases need to be specified. All the rest, configuration and code fix-ups are part of 'StateMachine' invocation.

horuable
Posts: 147
Joined: Sat Mar 06, 2021 12:35 am

Re: PIO "gotcha" - "set_init"

Fri May 07, 2021 3:58 pm

It's just a guess, but I think the division is there to simplify using multiple instances of PIO code. Like, if you have some communication protocol you'll always use the same number of pins, shift directions and/or autopull/autopush setup. This data goes into @asm_pio(). The things that can change, like exact pins and frequency are defined for specific SM. That way you don't have to instantiate every SM with parameters that are required by the PIO program (and should always be the same) since they are tied to the program itself.
Of course, it falls short for a protocol that can be used in say 8 or 16-bit mode, because then you need two functions with the only difference being pull/push thresholds.

hippy
Posts: 10269
Joined: Fri Sep 09, 2011 10:34 pm
Location: UK

Re: PIO "gotcha" - "set_init"

Fri May 07, 2021 6:18 pm

I would argue putting configuration in the '@asm_pio' limits reusability of the PIO code in StateMachines. A contrived example which would, if it worked, toggle one to five GPIO ...

Code: Select all

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

@asm_pio()
def ToggleUpToFivePins():
  set(pins, 0x1F)
  set(pins, 0x00)

# Toggle just one pin - Pin 24
sm0 = StateMachine(0, ToggleUpToFivePins, set_base=Pin(24))

# Toggle two pins - Pins 25 and 26
sm1 = StateMachine(1, ToggleUpToFivePins, set_base=Pin(25), set_count=2)
As it is 'set_count' doesn't exist and the number of pins specified and allowed by 'set_init' becomes hard-wired to the PIO sequence. One would need five different PIO sequences to cater for toggling one through five GPIO.

However one can hack round that by adjusting the PIO code wrapper ...

Code: Select all

# Toggle just one pin - Pin 24
ToggleUpToFivePins[-2] = [PIO.OUT_LOW]
sm0 = StateMachine(0, ToggleUpToFivePins, set_base=Pin(24))

# Toggle two pins - Pins 25 and 26
set_count = 2
ToggleUpToFivePins[-2] = [PIO.OUT_LOW] * set_count
sm1 = StateMachine(1, ToggleUpToFivePins, set_base=Pin(25))
Of course it's more complicated when there are a mix of input and output pins, but the point is it can be moved down into 'StateMachine', shouldn't be in '@asm_pio' in the first place IMO.

Actually not more complicated if it were just making 'set_init' a keyword for 'StateMachine' and that could warn when no 'set_init' were present when 'set_base' was specified rather than run but not work. There's actually a lot more checking it could do to identify user errors.

The best solution to maintain compatibility with what there is would be to allow 'set_init' to be specified in both, check that if it's in both that they are the same.

horuable
Posts: 147
Joined: Sat Mar 06, 2021 12:35 am

Re: PIO "gotcha" - "set_init"

Sat May 08, 2021 3:10 pm

I think that PIO code reusability comes from being able to run the exact same sequence on multiple SMs with a guarantee it'll behave the same every time. From what I understand, after reading documentation and various articles on PIO, its main point is to enable users to implement their own specific communication protocols that are otherwise unavailable on the chip. For this application, I think the current way MP works is just fine, since most (all?) protocols will have a predetermined number of IO that are used, and there's no need to explicitly set or change this number for every instance. The same goes for shift counts and autopush/autopull configuration - it's specific to the protocol and not the SM that is running it.
hippy wrote: One would need five different PIO sequences to cater for toggling one through five GPIO.
Or just use 5 SMs running the sequence meant to toggle single GPIO with the added benefit of controlling the frequency of every GPIO, not being forced to use 5 consecutive pins, and having the ability to start/stop any single GPIO at any time.
I think (and I might be wrong) that this is more along the lines of the intended use of PIO.

All in all, I'm not entirely opposed to the idea of being able to easily modify some parameters of the SM, I just think the current way of doing things is ok for most usecases. My approach would be to leave @asm_pio() as it is and treat parameters passed to it as defaults that can be changed using additional methods for StateMachine object after it has been instantiated.

Return to “MicroPython”