Skip to content

rp2040: add a background write with looping to StateMachines #6300

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

Merged
merged 8 commits into from
Apr 26, 2022

Conversation

jepler
Copy link

@jepler jepler commented Apr 19, 2022

This adds the ability to background-write to a StateMachine, either once or in a loop (or both). It can be seen in action driving servos in the parallel pull request adafruit/Adafruit_CircuitPython_PIOASM#41

  • buffers less than 8 entries long don't work right
  • exiting without deinit runs out of buffers
  • it seems to take "a long time" to switch buffers, but that may be because of how I have artificially long sequences
  • debug statements
  • traces of using 2 dma channels, can use just 1
  • update the pulsegroup example to the current API

The examples below worked with an older version of the PR:

"Servo Cluster" / "PWM Cluster" works: https://gist.github.com/jepler/5043843c9c196ad9d513e9f89e48ba79 as does this simpler test program:

import supervisor
import array
import board
import rp2pio
import adafruit_pioasm
import sys

# Two different blinks: long & short
a = array.array('H', [0xc0c0])
b = array.array('H', [0x6060])

delay = 1
text_program = f"""
    set pins 0 [{delay}]
    out x 8 [{delay}]
wait_hi:
    jmp x--, wait_hi  [{delay}]

    set pins 1 [{delay}]
    out x 8 [{delay}]
wait_lo:
    jmp x--, wait_lo  [{delay}]
"""

program = adafruit_pioasm.Program(text_program, build_debuginfo=True)

def cycle(*args):
    while True:
        yield from args

sm = rp2pio.StateMachine(program.assembled, frequency=2000, first_set_pin=board.D13, auto_pull=True, pull_threshold=16)

print("""
Press a key to change blink patterns. Note the delay of 8-9 blinks before
change, due to the 8-element depth of the PIO TX FIFO
"""
)
for signal in cycle(a, b):
    sm.start_continuous_write(signal)
    sys.stdin.read(1)

Closes: #6295

jepler added 6 commits April 19, 2022 11:35
the sequence has to be a minimum length, 8 entries, but this problem
is not detected. I don't THINK this is an insurmountable problem.
@jepler jepler marked this pull request as ready for review April 20, 2022 14:56
@jepler
Copy link
Author

jepler commented Apr 20, 2022

@ZodiusInfuser one use case for this is better support for the Pimoroni servo board! can you check it out and give some feedback? My implementation of 'pulsle group' based on your code is at https://gist.github.com/jepler/5043843c9c196ad9d513e9f89e48ba79 including a little servo-style demo. Once this is added to CircuitPython, we hope you'd be able to adapt your lib to have full functionality with CircuitPython.

@jepler
Copy link
Author

jepler commented Apr 20, 2022

Some logic captures of 'servo-like activity':

  • start times can be staggered
    image

  • start times can be synchronized
    image

  • or they can even be arbitrary
    image

  • If an active-time is such that it crosses the 0-counter value, then changes are NOT glitch free:
    image

@ZodiusInfuser
Copy link

@jepler Thanks for tagging me! This looks pretty cool, and would be great to have such functionality within CircuitPython!

My time is pretty limited for the next few weeks, so not sure if I will get chance to look at it in detail before next month. From a cursory glance though it seems your PulseGroup has a fair chunk of the functionality of my PWMCluster. It's funny how simple Python makes some of the code, like that one line sort 😆 .

Some logic captures of 'servo-like activity':

Great to see that staggered start!

  • If an active-time is such that it crosses the 0-counter value, then changes are NOT glitch free:

^ These took me so long to get rid of 😅, and is where much of the complexity in my PWMCluster came from. Don't know how important that would be for your implementation given it's for a more wider use.

@jepler
Copy link
Author

jepler commented Apr 20, 2022

Thanks @ZodiusInfuser ! If there's nothing that stands out as "obviously too broken to build on" we'll keep moving forward on this PR.

@jepler jepler requested a review from dhalbert April 20, 2022 16:06
@ZodiusInfuser
Copy link

Is it possible for it to auto switch to a new sequence after it has finished the first? To overcome some of the glitches I experienced, I ended up having PWMCluster generate an entry sequence and a looping sequence. The DMA would load in the entry sequence, and then if no new sequence was provided by the user, it would switch to the looping sequence then repeatedly do that. This entry sequence let the system finish off any outstanding pulses cleanly and start the new ones the user queued (which was mainly an issue for those overlapping the 0-counter).

There was also the triple buffering thing I did, where the new sequence buffer would only be provided to the DMA when it is fully generated. Sounds like that could already be handled by what you have written?

Also, a "nice to have" feature that's on my own to-do list is some kind of interrupt callback when a sequence is loaded or looped. My intention being that on my walking robots I want to know when new data has been loaded, so I know I have 20ms to calculate the IK for the next sequence if I want the absolute lowest latency. For my C++ implementation I was considering having the DMA interrupt trigger a timer interrupt for a few microseconds later, so as to not stall the PIO.

@ZodiusInfuser
Copy link

ZodiusInfuser commented Apr 20, 2022

Oh, another thing. I don't know if CircuitPython already handles PIO cleanup nicely already, but I had many headaches getting it working for the PWMCluster. Ended up discovering a bug with DMA's and in-flight interrupts. https://github.com/pimoroni/pimoroni-pico/blob/7dd1c608619a363a2b531d38a9e5dbc813a8ea6f/drivers/pwm/pwm_cluster.cpp#L151

@jepler
Copy link
Author

jepler commented Apr 20, 2022

as is, there's no way to guarantee a sequence plays at most once, but if you call the start_continuous_write signal again before the sequence is done that's what will happen. Of course, a GC pause or something else could leap out at just the wrong moment and gnaw your face off..

@jepler
Copy link
Author

jepler commented Apr 20, 2022

I'm wondering if a different API is better. `def background_write(*buffers: ReadableBuffer, loop=Optional[ReadableBuffer]): ...

This would 'play' each positional argument buffer exactly once, and then play the loop kwarg buffer forever, if given. If no loop, then it would stop instead of looping anything.

This change would be to enable the use case where a special 'transitional' output needs to be played just once, to do with a change in PWM duty cycle that crosses the buffer wrap point. It also enables the use case where you want to write something just once, but in the background.

This would complicate the implementation a bit, but if it's useful then so be it.

jepler added 2 commits April 23, 2022 13:09
Now a 'once' and a 'loop' buffer can be specified.

'once' is useful for things like writing a neopixel strip in the background,
if you can guarantee the buffer contents are stable until the write is complete.

'loop' is useful for periodic things, like pwm & servos.

both together are useful for some special cases of pwm/servo, where a
transitional waveform needs to be played for one repetition and then
a new waveform needs to be played after that.

The API is renamed to reflect that it's a more generic 'background'
operation.
@jepler jepler changed the title rp2040: add a continuous write mode to StateMachines rp2040: add a background write with looping to StateMachines Apr 23, 2022
@jepler
Copy link
Author

jepler commented Apr 23, 2022

I ended up adding support for one 'once' buffer and one 'loop' buffer. this covers the glitch-free pwm case, and also the simplest case of non-looping background writing.

@jepler jepler added this to the 7.3.0 milestone Apr 24, 2022
Copy link
Collaborator

@dhalbert dhalbert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A great addition! I tested this with the gist example, and I get random movement of servos, as expected. The examples use start_continuous_write() instead of the newer name background_write().

@dhalbert dhalbert merged commit c8e8171 into adafruit:main Apr 26, 2022
@jepler
Copy link
Author

jepler commented Apr 26, 2022

whoops, I thought I'd updated the PR in adafruit_pioasm. I'll double check. Thanks for the merge!

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

Successfully merging this pull request may close these issues.

rp2040: add a pio mode that can loop a buffer forever
3 participants