Software triggered PWM
I have an application that requires me to trigger a DMA transfer every N software loop cycles. One core is running a CIC filter and the DMA signals the second core to run the comb part. The loop is running a very optimized DSP algorithm and I am looking for ways to not have to deal with the overhead of having to keep track of a counter and launching the DMA.
Browsing the datasheet I have found that a PWM can trigger a DMA transfer on wrap but I do not see a way to increment the accumulator in the PWM channel in software. The only option I see is putting a PWM channel into DIVMODE=RISE and then physically connecting the PIN B to an output pin and pulsing that in software.
Would this work? Is there a better way to produce a DMA transfer every N software loops?
Browsing the datasheet I have found that a PWM can trigger a DMA transfer on wrap but I do not see a way to increment the accumulator in the PWM channel in software. The only option I see is putting a PWM channel into DIVMODE=RISE and then physically connecting the PIN B to an output pin and pulsing that in software.
Would this work? Is there a better way to produce a DMA transfer every N software loops?
Re: Software triggered PWM
What do you mean, the DMA signals the second core to run? Why can't you just use simple timed interrupt to wake up (or preempt) the second core?
Are you talking about using the PWM as a pacing timer? Because as far as I know PWM slices can't trigger DMA transfers directly, quoting the datasheet:simont153 wrote: ↑Wed Jul 09, 2025 4:29 pmBrowsing the datasheet I have found that a PWM can trigger a DMA transfer on wrap but I do not see a way to increment the accumulator in the PWM channel in software. The only option I see is putting a PWM channel into DIVMODE=RISE and then physically connecting the PIN B to an output pin and pulsing that in software.
You can have a dummy DMA channel that's artificially slowed by a PWM slice that triggers another channel to more or less achieve periodic transfers, though with jitter if any of the transfers stall at any point.The following events can
trigger a channel:
• A write to a channel trigger register.
• Completion of another channel whose CHAIN_TO points to this channel.
• A write to the MULTI_CHAN_TRIGGER register (can trigger multiple channels at once).
You can modify the PWM accumulator by modifying the CH0_CTR register, but I have no idea why you'd want to do that, the hole point of a PWM peripheral is that it's supposed to count on it's own? Why would you want to increment that in software?
Re: Software triggered PWM
For pacing trigger, there are 4 dedicated DMA timers.
BTW, I think PWM paced transfers can be useful when there's no FIFO available.
PWM accumulator means counter?
BTW, I think PWM paced transfers can be useful when there's no FIFO available.
PWM accumulator means counter?
Re: Software triggered PWM
I think I did a very mediocre job of explaining my problem.
I have code that looks like this:
I am trying to optimize out the counter increment, dma software trigger and counter reset. I know it may seem silly but looking at the assembly it represents around 10 instructions which is a significant percentage of the work done in my tight loop.
I am seeking a way to increment some hardware register that will automatically trigger a DMA and reset itself when it reaches a predefined count.
I have code that looks like this:
Code: Select all
int counter = 0;
while (true) {
// some DSP algorithm that executes in roughly 300ns
do_tight_loop();
if (++counter > N) {
dma_channel_start(chan_num);
counter = 0
}
}
I am seeking a way to increment some hardware register that will automatically trigger a DMA and reset itself when it reaches a predefined count.
Re: Software triggered PWM
Hi,
the 'advance phase' feature of the PWM might be what you seek. In the SDK the counter can be advanced with pwm_advance_count(), but since you have an assembler implementation you are probably more interested in the PWM register PWM_CHx_CSR - setting bit 7 (using the XOR-variant of the register address) will advance the counter of the corresponding channel. The counter wrap-around can be used to trigger a DREQ event, which amounts to one, at most, 4 byte transfer by the attached DMA channel. To move more bytes you will need to use this 4-byte transfer to trigger another DMA channel to accomplish the actual transfer. Additionally, if the code needs to work on the RP2040, this secondary DMA channel needs to re-trigger the first DMA channel, since otherwise the fist channel can only handle a finite number of requests (the initial transfer count) - on the RP2350 the count can be infinite.
1012
the 'advance phase' feature of the PWM might be what you seek. In the SDK the counter can be advanced with pwm_advance_count(), but since you have an assembler implementation you are probably more interested in the PWM register PWM_CHx_CSR - setting bit 7 (using the XOR-variant of the register address) will advance the counter of the corresponding channel. The counter wrap-around can be used to trigger a DREQ event, which amounts to one, at most, 4 byte transfer by the attached DMA channel. To move more bytes you will need to use this 4-byte transfer to trigger another DMA channel to accomplish the actual transfer. Additionally, if the code needs to work on the RP2040, this secondary DMA channel needs to re-trigger the first DMA channel, since otherwise the fist channel can only handle a finite number of requests (the initial transfer count) - on the RP2350 the count can be infinite.
1012
Re: Software triggered PWM
Thank you, this is almost exactly what I was looking for.
I have tried to implement it however I did not find a way to completely isolate the PWM channel from system clock and have a single phase advance increment the counter.
Would you run the channel with EN bit off to gate it from the rest of the chip? My cursory attempt at this, and setting the divider to 1 did not produce expected results.
I have achieved a similar result with a short PIO program (using FIFO pushes and pulls to signal processor and DMA). It did require many DMA channels to configure and do the actual transfer.
I have tried to implement it however I did not find a way to completely isolate the PWM channel from system clock and have a single phase advance increment the counter.
Would you run the channel with EN bit off to gate it from the rest of the chip? My cursory attempt at this, and setting the divider to 1 did not produce expected results.
I have achieved a similar result with a short PIO program (using FIFO pushes and pulls to signal processor and DMA). It did require many DMA channels to configure and do the actual transfer.
Re: Software triggered PWM
So, all that you want is to increment PWM counter from software?
I can try some tricks from debugger.
You can also use inline assembler, a processor register, interpolator...
Still don't understand why you are trying to use such an intricate way to trigger the DMA.
I can try some tricks from debugger.
You can also use inline assembler, a processor register, interpolator...
Still don't understand why you are trying to use such an intricate way to trigger the DMA.
Re: Software triggered PWM
It seems that PH_ADV is only useful if you already have a controlled input pulse since it only affects the behavior of the clock divider and does not explicitly generate counts.simont153 wrote: ↑Sun Jul 13, 2025 6:16 pmThank you, this is almost exactly what I was looking for.
I have tried to implement it however I did not find a way to completely isolate the PWM channel from system clock and have a single phase advance increment the counter.
Would you run the channel with EN bit off to gate it from the rest of the chip? My cursory attempt at this, and setting the divider to 1 did not produce expected results.
I have achieved a similar result with a short PIO program (using FIFO pushes and pulls to signal processor and DMA). It did require many DMA channels to configure and do the actual transfer.
However, going back to your original idea about driving pin B of the PWM slice from another pin by an external connection - that should not be necessary since you can instead drive the state of the pin directly by using the GPIO register GPIOx_CTRL. Specifically, in the register GPIOx_CTRL the input state of pin 'x' as seen by the peripheral (PWM) can be assigned by bits 16:17 (INOVER) to 0x0 or 'NORMAL' (PWM input from physical pin), to 0x1 or 'INVERT' (PWM input inverse of physical pin), to 0x2 (PWM input LOW) or to 0x3 (PWM input HIGH). Hence, you should get what you want if you can sacrifice one GPIO pin (one of the two assigned to pin B of the PWM) and two GPIO register accesses for the pin B toggle (set bit 16 for LOW->HIGH, clear bit 16 for HIGH->LOW) - or if your counts are always even you can halve the count and get away with one GPIO register access (xor 1 to bit 16) - in all cases bit 17 must be set beforehand to enable the 'explicit mode'.
1012
Re: Software triggered PWM
I'm still not sure if I understand your problem. Does your "do_tight_loop()" run in variable time and that's why you're trying to manually increment the PWM counter?simont153 wrote: ↑Fri Jul 11, 2025 3:23 amI am trying to optimize out the counter increment, dma software trigger and counter reset. I know it may seem silly but looking at the assembly it represents around 10 instructions which is a significant percentage of the work done in my tight loop.Code: Select all
int counter = 0; while (true) { // some DSP algorithm that executes in roughly 300ns do_tight_loop(); if (++counter > N) { dma_channel_start(chan_num); counter = 0 } }
I am seeking a way to increment some hardware register that will automatically trigger a DMA and reset itself when it reaches a predefined count.
If that's the case the only option you have AFAIK is using a PIO state machine and 3 (2 for the RP2350) DMA channels. In your while loop you'd then write a (arbitrary) value to the state machine FIFO taking 1 instruction and 2 clock cycles assuming no bus contention. The PIOs are on the AHB-Lite splitter together with USB on the RP2040 so that should be manageable.
The PIO code should look something like this:
Code: Select all
.wrap_target
mov x, y ; reload counter
counter_loop:
pull block
jmp x-- counter_loop
in NULL, 32
.wrap
Re: Software triggered PWM
How does it look?Tharre wrote: I am trying to optimize out the counter increment, dma software trigger and counter reset. I know it may seem silly but looking at the assembly it represents around 10 instructions which is a significant percentage of the work done in my tight loop.
How many loops you need for a DMA trigger?
- PicoTinker
- Posts: 110
- Joined: Sun Mar 07, 2021 3:52 am
Pacing (X/Y) fractional timer
How do they actually work? Initial X/Y values are 0/0: Does this mean stop?
Is the accumulator cleared when the register is written or is the phase arbitrary?
If not cleared, could something like 0/1 for X/Y drain the accumulator to 0, and it would start with a specific phase for next X > 0?
https://rpltd.co/rp2040-datasheet wrote: DMA: TIMER0, TIMER1, TIMER2, TIMER3 Registers
Pacing (X/Y) fractional timer
The pacing timer produces TREQ assertions at a rate set by ((X/Y) * sys_clk). This equation is evaluated every
sys_clk cycles and therefore can only generate TREQs at a rate of 1 per sys_clk (i.e. permanent TREQ) or less.Code: Select all
Bits Description Type Reset 31:16 X: Pacing Timer Dividend. Specifies the X value for the (X/Y) fractional timer. RW 0x0000 15:0 Y: Pacing Timer Divisor. Specifies the Y value for the (X/Y) fractional timer. RW 0x0000
Could they maybe be implemented somewhat like:
Code: Select all
// called at rate of clk_sys; returns TREQ
bool divide(uint16_t x, uint16_t y) {
static uint16_t acc = 0;
acc += x;
if (acc >= y) {
acc -= y;
return true;
}
return false;
}Code: Select all
// similar to https://github.com/Wren6991/libfpga/blob/master/common/clkdiv_frac.v
// integer + fractional divider with 1st-order delta sigma pulse swallowing
bool divide(bool enable, bool divider_restart, uint32_t divisor) {
divisor &= ~0xff; // truncate to 16.8 fixed-point
static uint32_t counter = 0;
const uint32_t one = 1 << 16; // 1.0 in 16.16 fixed-point
if (divider_restart) {
counter = 0;
return false;
}
if (counter >= one) {
counter -= one;
return false;
} else {
counter -= one;
counter += divisor;
return enable;
}
}Re: Software triggered PWM
I think it's controlled from control registers/aliases.PicoTinker wrote: gmx wrote: ↑
10 Jul 2025, 02:03
For pacing trigger, there are 4 dedicated DMA timers.
How do they actually work? Initial X/Y values are 0/0: Does this mean stop?
Is the accumulator cleared when the register is written or is the phase arbitrary?
If not cleared, could something like 0/1 for X/Y drain the accumulator to 0, and it would start with a specific phase for next X > 0?
I don't know if the counter/timers are reset.
22:17 TREQ_SEL: Select a Transfer Request signal.
The channel uses the transfer request signal to pace its data transfer rate.
Sources for TREQ signals are internal (TIMERS) or external (DREQ, a Data
Request from the system).
0x0 to 0x3a → select DREQ n as TREQ
RW 0x00
Enumerated values:
0x3b → TIMER0: Select Timer 0 as TREQ
0x3c → TIMER1: Select Timer 1 as TREQ
0x3d → TIMER2: Select Timer 2 as TREQ (Optional)
0x3e → TIMER3: Select Timer 3 as TREQ (Optional)
0x3f → PERMANENT: Permanent request, for unpaced transfers.
Re: Software triggered PWM
Thank you all for your inputs. I forgot to close out this thread with the solution that I ended up using.
I ended up having my software loop push a dummy value to a PIO state machine that kept track of the number of pushes on its TX FIFO. When the required number was counted it would push a value to the RX FIFO. A DMA channel was setup to clear the RX FIFO and set off a chain of other DMAs that both configured and fired the actual DMA that my software required.
I ended up having my software loop push a dummy value to a PIO state machine that kept track of the number of pushes on its TX FIFO. When the required number was counted it would push a value to the RX FIFO. A DMA channel was setup to clear the RX FIFO and set off a chain of other DMAs that both configured and fired the actual DMA that my software required.
Jump to
- Community
- General discussion
- Announcements
- Other languages
- Deutsch
- Español
- Français
- Italiano
- Nederlands
- 日本語
- Polski
- Português
- Русский
- Türkçe
- User groups and events
- Raspberry Pi Official Magazine
- Using the Raspberry Pi
- Beginners
- Troubleshooting
- Advanced users
- Assistive technology and accessibility
- Education
- Picademy
- Teaching and learning resources
- Staffroom, classroom and projects
- Astro Pi
- Mathematica
- High Altitude Balloon
- Weather station
- Programming
- C/C++
- Java
- Python
- Scratch
- Other programming languages
- Windows 10 for IoT
- Wolfram Language
- Bare metal, Assembly language
- Graphics programming
- OpenGLES
- OpenVG
- OpenMAX
- General programming discussion
- Projects
- Networking and servers
- Automation, sensing and robotics
- Graphics, sound and multimedia
- Other projects
- Media centres
- Gaming
- AIY Projects
- Hardware and peripherals
- Camera board
- Compute Module
- Official Display
- HATs and other add-ons
- Device Tree
- Interfacing (DSI, CSI, I2C, etc.)
- Keyboard computers (400, 500, 500+)
- Raspberry Pi Pico
- General
- SDK
- MicroPython
- Other RP2040 boards
- Zephyr
- Rust
- AI Accelerator
- AI Camera - IMX500
- Hailo
- Software
- Raspberry Pi OS
- Raspberry Pi Connect
- Raspberry Pi Desktop for PC and Mac
- Beta testing
- Other
- Android
- Debian
- FreeBSD
- Gentoo
- Linux Kernel
- NetBSD
- openSUSE
- Plan 9
- Puppy
- Arch
- Pidora / Fedora
- RISCOS
- Ubuntu
- Ye Olde Pi Shoppe
- For sale
- Wanted
- Off topic
- Off topic discussion