What is the "usual solution" that I'm missing or that I'm excluding here?I'm rather more interested in the OP's use case. It is easier to justify this kind of thing when there is a really compelling or necessary use case. The FIFO feeding long timing counts per the PWM example seems to be sensible, but what is it with the OP's use case that excludes the usual solution?
For reference here's the concrete example from my immediate use case:
I've been building a WS2812B hub/multiplexer using a RP2040 (125MHz) that can receive input on 4 channels and transmit output to 16 channels.
Ideally, I want my rx and tx loops to handle all the WS2812B-data to multi-byte-pixel conversions and multi-byte-pixel to WS2812B-data conversions asynchronously via DMA & PIO only, so that the cpus have enough cycles remaining to intelligently route/process segments of the input channels' pixel data to the appropriate output channels.
Originally, I had considered DMA'ing all the rx PIO output into a fixed size ring-buffer and setting a pollable bit via DMA after each frame completed. In this scheme the PIO program would count along and pad bytes after the end of an input frame when less than the buffer length, or stop collecting bytes before the end of an input frame when greater than the buffer length. This scheme wasn't workable. I couldn't balance the count of available PIO registers vs the count of available program instructions to get it to work. It needed to track 3 countdowns in the PIO: 1 quite-period counter (10us) to prevent starting the rx data capture mid-frame, 1 reset-period counter (50us) to detect the WS2812B end-of-frame reset-period, and 1 output-buffer counter to track bytes received vs buffer-length.
Instead, with my current implementation (see included code below) I'm using cpu0 as an interrupt handler: the PIO marks an interrupt after each frame is received, the DMA limits the transfers to prevent buffer overflows, and the interrupt handler records the number of bytes received, resets the DMA, resets the PIO rxfifo, and resets the PIO interrupt after each frame. In this scheme the PIO program only tracks 2 countdowns: 1 quite-period counter and 1 reset-period counter. Each of these counter values is initialized to an approximated (5 high bits) value in 4 instructions, using both the destination scratch register and the osr register, before being stored in the destination scratch register. At runtime, when these counters are needed, I first temporarily stash their initial values in the osr register, run the countdown, and then restore the their initial value from the osr register.
My proposed LOAD instruction could be put to good use in my program:
1. Instead of initializing both the scratch registers to my initial countdown values upfront, I could initialize the countdown values on demand, and repurpose the scratch registers between countdown runs. This would facilitate tracking more than 2 non-concurrent countdowns in a single PIO program.
2. Instead of rendering an approximate initial value in 4 instructions and consuming the osr register, I could set a precise value in just 1 instruction while consuming just the destination register and two lines of program memory.
3. I could avoid temporarily stashing initial values in and consuming the osr register for that purpose while a countdown was running, and leave the register free for other uses.
4. I could easily emit pre-defined delimiter tokens (like Start-Of-Frame/End-Of-Frame) into the rxfifo stream or pinouts.
Code:
; Copyright (C) 2024 Steffen Yount;; Licensed under the Apache License, Version 2.0 (the "License");; you may not use this file except in compliance with the License.; You may obtain a copy of the License at;; http://www.apache.org/licenses/LICENSE-2.0;; Unless required by applicable law or agreed to in writing, software; distributed under the License is distributed on an "AS IS" BASIS,; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.; See the License for the specific language governing permissions and; limitations under the License..program gpio_pins_to_rx_bytes.origin 0; compose our countdown targets into reg x and y, for the pin quiet-period and the pixel reset-period respectively set x, 20 ; pin quiet-period timer for (640 * 2) ticks (10.24us) mov osr, x out null, 5 ; (20 << 5) = 640 mov x, osr set y, 25 ; pixel reset-period timer for (3200 * 2) ticks (51.2us) mov osr, y out null, 7 ; (25 << 7) = 3200 mov y, osr.wrap_target; ensure there's been at least 1 bytes worth (~10us) of inactivity on the pin before we start collecting data mov osr, x ; stash a copy of the pin quiet-period countdown targetpin_is_busy: mov x, osr ; reload the pin quiet-period countdown target wait 0 pin 0 ; get to known state: starting with pin 0 lowawaiting_activity: jmp pin pin_is_busy ; if EXECCTRL_JMP_PIN is high jmp x-- awaiting_activity mov x, osr ; countdown completed, restore the pin quiet-period countdown target into reg x; pin is inactive, start collecting dataawaiting_first_bit: mov osr, y ; stash a copy of the pixel reset-period countdown target wait 1 pin 0 ; wait for pin 0 (rel to PINCTRL_IN_BASE) to go highpin_is_high: nop [30] ; wait ~63 ticks (504ns) then see if this is an ON or OFF bit mov y, osr [30] ; reload the pixel reset-period countdown target in pins, 1 ; read datum from pin 0 (rel to PINCTRL_IN_BASE) push iffull noblock ; push 1-byte at a time: SHIFTCTRL_PUSH_THRESH = 8 bits wait 0 pin 0 ; wait for pin 0 (rel to PINCTRL_IN_BASE) to go lowawaiting_next_bit: jmp pin pin_is_high ; if EXECCTRL_JMP_PIN is high jmp y-- awaiting_next_bit mov y, osr ; countdown completed, restore the pixel reset-period countdown target into reg y; handle pixel reset timeout mov isr, null ; discard isr contents and reset its counter to zero irq wait 0 rel ; notify the irq handler that we've collected our last byte for this frame.wrap% c-sdk {static inline void gpio_pins_to_rx_bytes_program_init(PIO pio, uint sm, uint offset, uint pin) { // rx GPIO PIN gpio_set_function_no_side_effects(pin, GPIO_FUNC_PIO0); pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false); // init sm_cfg values pio_sm_config sm_cfg = gpio_pins_to_rx_bytes_program_get_default_config(offset); // make RXF 8 words deep sm_config_set_fifo_join(&sm_cfg, PIO_FIFO_JOIN_RX); // OUT shifts to left, no autopull sm_config_set_out_shift(&sm_cfg, false, false, 32u); // IN shifts to left, no autopush sm_config_set_in_shift(&sm_cfg, false, false, 8u); sm_config_set_in_pins(&sm_cfg, pin); sm_config_set_jmp_pin(&sm_cfg, pin); pio_sm_init(pio, sm, offset, &sm_cfg); pio_sm_set_enabled(pio, sm, true);}static inline void init_pio0_for_gpio_pins_to_rx_bytes_programs() { uint offset = pio_add_program(pio0, &gpio_pins_to_rx_bytes_program); gpio_pins_to_rx_bytes_program_init(pio0, 0u, offset, 5u); gpio_pins_to_rx_bytes_program_init(pio0, 1u, offset, 6u); gpio_pins_to_rx_bytes_program_init(pio0, 2u, offset, 24u); gpio_pins_to_rx_bytes_program_init(pio0, 3u, offset, 25u);}%}
Statistics: Posted by steffenyount — Thu Dec 19, 2024 8:51 pm