I've written some Python to drive a LED bar (https://thepihut.com/products/bi-color- ... -pack-of-2) from a PIO program fed by a chained pair of DMAs. The LEDs are multiplexed so only 1/3 can be driven at any one time. The PIO+DMA combo takes care of switching the signals without any CPU involvement.
I do have it working, but a few questions have come up...
Q1. If the first version of the PIO program is fine, why does the second one raise a PIOASMError: delay too large error (on the line with the comment)?
Good:Bad:I don't actually need the additional delay - the loop is already suitably slow, but the same error came up in an earlier version of the program when I was using pull(block) and mov(pins) (I think), and I'd like to understand why a previous/subsequent instruction alters the number of wait bits available to other instructions.
Q2. For the chaining, I need the secondary DMA to be configured to reset the read address of the data-mover DMA. The result I've ended up with feels quite clunky - I need to pass the address of a location that contains the address of the start of the source data. I'm sure there must be a cleaner way than what I have here, but it eludes me:
I do have it working, but a few questions have come up...
Q1. If the first version of the PIO program is fine, why does the second one raise a PIOASMError: delay too large error (on the line with the comment)?
Good:
Code:
@rp2.asm_pio(out_init=(rp2.PIO.OUT_LOW,) * 8, sideset_init=(rp2.PIO.OUT_LOW,) * 3, autopull=True, pull_thresh=8)def cycle_multiplex(): out(pins, 8).side(1) [3] out(pins, 8).side(2) [3] out(pins, 8).side(4) [3]Code:
@rp2.asm_pio(out_init=(rp2.PIO.OUT_LOW,) * 8, sideset_init=(rp2.PIO.OUT_LOW,) * 3, autopull=True, pull_thresh=8)def cycle_multiplex(): out(pins, 8).side(1) [3]# Now I'm only allowed a single bit of wait nop() [3] out(pins, 8).side(2) [3] nop() [3] out(pins, 8).side(4) [3] nop() [3]Q2. For the chaining, I need the secondary DMA to be configured to reset the read address of the data-mover DMA. The result I've ended up with feels quite clunky - I need to pass the address of a location that contains the address of the start of the source data. I'm sure there must be a cleaner way than what I have here, but it eludes me:
Code:
move = rp2.DMA()ctrl = rp2.DMA()self.values = bytearray(3 * 4)pointer = bytearray(4)addr = addressof(self.values)# Q. Is there a nicer way of doing this?pointer[0] = addr & 0xffpointer[1] = (addr >> 8) & 0xffpointer[2] = (addr >> 16) & 0xffpointer[3] = (addr >> 24) & 0xffDATA_REQUEST_INDEX = ((self.sm // 4) << 3) + (self.sm % 4)# Configure the 'move' channel to:# - transfer 32-bit words# - incrementing its read address# - *not* incrementing its write address# - chaining to the control channel# - using the DREQ for the state machinemove_cfg = move.pack_ctrl( chain_to = ctrl.channel, size = 2, inc_read = True, inc_write = False, treq_sel = DATA_REQUEST_INDEX )move.config( read = self.values, write = self.multiplexer, count = len(self.values) // 4, ctrl = move_cfg, trigger = False )# Configure the 'ctrl' channel to:# - transfer 32-bit words# - *not* incrementing either address# - chaining to the move channelctrl_cfg = ctrl.pack_ctrl( chain_to = move.channel, size = 2, inc_read = False, inc_write = False )ctrl.config( read = pointer, write = addressof(move.registers[0:1]), count = 1, ctrl = ctrl_cfg, trigger = False )# And finally, start one of the channels# (chaining will star the other)ctrl.active(1)Statistics: Posted by chrisjl — Wed Sep 10, 2025 6:18 pm