drivers: pwm: add support for enabling DMA requests#88671
drivers: pwm: add support for enabling DMA requests#88671aescolar merged 1 commit intozephyrproject-rtos:mainfrom
Conversation
|
Hello @SiViSur, and thank you very much for your first pull request to the Zephyr project! |
fa368da to
39dd7af
Compare
|
I've decided to add a small example app using the added API. Please find all relevant files in this zip Here's some snippets of the source code: #include <zephyr/kernel.h> // printk
#include "audio_data.h"
#include "led_blinky.h"
#include "pwm_setup.h"
int main(void) {
printk("Zephyr Example Application\n");
/************************ Speaker PWM setup begin ************************/
if (start_pwm()) {
return 0;
}
/************************* Speaker PWM setup end *************************/
/************************ Speaker DMA setup begin ************************/
if (start_dma_for_pwm()) {
return 0;
}
/************************* Speaker DMA setup end *************************/
/*********************** LED blinky example begin ************************/
if (setup_blinky_led()) {
printk("Failed to setup blinky LED\n");
return 0;
}
/************************ LED blinky example end *************************/
while (1) {
if (toggle_led()) {
return 0;
}
k_msleep(SLEEP_TIME_MS);
}
return 0;
}pwm_setup.h #ifndef PWM_SETUP_H
#define PWM_SETUP_H
#include <zephyr/device.h>
#include <zephyr/drivers/clock_control/stm32_clock_control.h>
#include <zephyr/drivers/dma.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/kernel.h> // printk
#include "stm32wb55xx.h"
#include "stm32wbxx_ll_tim.h"
/* Define the buzzer PWM device */
#define BUZZER_PWM_NODE DT_ALIAS(pwm_speaker)
#if !DT_NODE_HAS_STATUS(BUZZER_PWM_NODE, okay)
#error "Speaker PWM device not found or not enabled in device tree"
#endif
/* Static PWM device reference */
static const struct device *pwm_dev = DEVICE_DT_GET(BUZZER_PWM_NODE);
/* Define the DMA device node */
#define DMA2_DEVICE_NODE DT_ALIAS(dma_speaker)
#if !DT_NODE_HAS_STATUS(DMA2_DEVICE_NODE, okay)
#error "DMA device not found or not enabled in device tree"
#endif
/* Static DMA device reference */
static const struct device *dma2_dev = DEVICE_DT_GET(DMA2_DEVICE_NODE);
static uint32_t dma_callback_count = 0;
int start_pwm() {
// Retrieve PWM device node for speaker pins
if (!device_is_ready(pwm_dev)) {
printk("Error: PWM device not ready\n");
return -ENODEV;
}
printk("Enabling PWM\n");
// Start PWM on TIM2 channel 1 (PA15)
if (pwm_set(pwm_dev, 1, PWM_USEC(23), PWM_USEC(11), 0)) {
printk("PWM channel 1 (PA15) start failed\n");
return -1;
}
return 0;
}
void dma_callback(const struct device *dev, void *user_data, uint32_t channel,
int status) {
dma_callback_count++;
printk("DMA Callback count: %d\n", dma_callback_count);
}
int start_dma_for_pwm() {
// Retrieve DMA2 device node for PWM duty cycle DMA requests
if (!device_is_ready(dma2_dev)) {
printk("Error: DMA2 device not ready\n");
return -ENODEV;
}
// Retrieve PWM device node for speaker pins
if (!device_is_ready(pwm_dev)) {
printk("Error: PWM device not ready\n");
return -ENODEV;
}
printk("Enabling DMA interrupts on TIM2 CH1\n");
// Enable DMA interrupts on TIM2 CH1
pwm_enable_dma(pwm_dev, 1);
// Configure the DMA
struct dma_config dma_cfg = {0};
struct dma_block_config dma_block = {0};
/* Get the base address of Timer 2 */
uint32_t timer2_base = DT_REG_ADDR(DT_NODELABEL(timers2));
// #define DMA_REQUEST_TIM2_CH1 0x0000001CU
dma_cfg.dma_slot = 0x0000001CU;
dma_cfg.channel_direction = MEMORY_TO_PERIPHERAL;
dma_cfg.complete_callback_en = true;
dma_cfg.error_callback_dis = true;
dma_cfg.channel_priority = 0x2; /* high priority */
dma_cfg.source_data_size = 4; /* 32-bit data */
dma_cfg.dest_data_size = 4; /* 32-bit data */
dma_cfg.source_burst_length = 1;
dma_cfg.dest_burst_length = 1;
dma_cfg.block_count = 1;
dma_cfg.head_block = &dma_block;
dma_cfg.dma_callback = dma_callback;
// Set up DMA block for circular transfer
dma_block.block_size = AUDIO_DATA_SIZE * sizeof(uint32_t);
dma_block.source_address = (uint32_t)audioData_high;
dma_block.dest_address =
(uint32_t)timer2_base + 0x34; /* CCR1 offset is 0x34 */
dma_block.source_addr_adj = DMA_ADDR_ADJ_INCREMENT;
dma_block.dest_addr_adj = DMA_ADDR_ADJ_NO_CHANGE;
dma_block.source_reload_en = true; /* circular mode */
dma_block.dest_reload_en = true; /* circular mode */
/* Start the DMA transfer on channel 1 */
if (dma_config(dma2_dev, 1, &dma_cfg) != 0) {
printk("DMA2 config failed\n");
return -EIO;
}
if (dma_start(dma2_dev, 1) != 0) {
printk("DMA2 start failed\n");
return -EIO;
}
return 0;
}
#endif /* PWM_SETUP_H */ |
If this sample code is useful, would you add a new commit with them :--) |
What do you mean? Creating a |
|
This pull request has been marked as stale because it has been open (more than) 60 days with no activity. Remove the stale label or add a comment saying that you would like to have the label removed otherwise this pull request will automatically be closed in 14 days. Note, that you can always re-open a closed pull request at any time. |
|
@rruuaanng Any advice on how to proceed with this MR? Is it usual to take this much time to get comments? |
|
@anangl Can you comment on the api change ? |
|
This pull request has been marked as stale because it has been open (more than) 60 days with no activity. Remove the stale label or add a comment saying that you would like to have the label removed otherwise this pull request will automatically be closed in 14 days. Note, that you can always re-open a closed pull request at any time. |
What do you mean @JarmouniA? How do I do that? |
zephyr/include/zephyr/drivers/pwm.h Line 22 in e7c454c https://docs.zephyrproject.org/latest/develop/api/overview.html#api-overview |
Thanks! Done. |
Extend the PWM API with optional API functions for enabling DMA requests Possible solution for zephyrproject-rtos#88670 Signed-off-by: Vincent Surkijn <vincent.surkijn@siemens.com>
|
juickar
left a comment
There was a problem hiding this comment.
Nit but non-blocking: update copyrights to 2026
|
@anangl PTAL |
| uint32_t channel) | ||
| { | ||
| K_OOPS(K_SYSCALL_DRIVER_PWM(dev, enable_dma)); | ||
| return z_impl_pwm_enable_dma((const struct device *)dev, channel); |
There was a problem hiding this comment.
Why is there no indentation here?
etienne-lms
left a comment
There was a problem hiding this comment.
Some minor comments, otherwise LGTM.
| K_OOPS(K_SYSCALL_DRIVER_PWM(dev, enable_dma)); | ||
| return z_impl_pwm_enable_dma((const struct device *)dev, channel); |
There was a problem hiding this comment.
Cast not needed.
Could you indent these instructions and those in z_vrfy_pwm_disable_dma()?
Also fix indentation for the functions 2nd argument, here:
static inline int z_vrfy_pwm_enable_dma(const struct device *dev,
uint32_t channel)By the way, it seems the cast to (const struct device *) are not needed, but present above so fair enough for consistency. Maybe to clean is a later change.
|
|
||
| #ifdef CONFIG_PWM_WITH_DMA | ||
| static int pwm_stm32_enable_dma(const struct device *dev, | ||
| uint32_t channel) |
| const struct pwm_stm32_config *cfg = dev->config; | ||
|
|
||
| /* DMA requests are only supported on Capture/Compare channels. | ||
| * However, these DMA request can also be used in PWM output mode to |
There was a problem hiding this comment.
| * However, these DMA request can also be used in PWM output mode to | |
| * However, these DMA requests can also be used in PWM output mode to |
|
Hi @SiViSur! To celebrate this milestone and showcase your contribution, we'd love to award you the Zephyr Technical Contributor badge. If you're interested, please claim your badge by filling out this form: Claim Your Zephyr Badge. Thank you for your valuable input, and we look forward to seeing more of your contributions in the future! 🪁 |
|
@SiViSur would be so kind as to follow up with a PR fixing the formatting issues? |
Sure! I'll try to do so later this week. |
(Little late to this, just stumbled upon this when evaluating how to use the STM32 timers for sending ws2812 data sequences) It would be really nice to have a sample in the repo that actually exercises this functionality, too, so folks don't need to search for this issue to understand how to use this new feature. Thanks for working on this! |
@petejohanson You're right. I'll try to include this in the follow-up MR that @aescolar requested. It will take me a bit longer though since I will have to test it on the board, but I think it's worth it. |
|
It is also shame that enhancement does not give the API to get timer base or at least pointer to CCRn register, cause that is the only thing missing for complete dma setup. Without that api, you either have to guess the timer unit, or to get it as parent of pwm. #define PWM_TIMER_BASE(inst) I've also got to this PR some days ago, also looking for ways to implement compatible = "worldsemi,ws2812-pwm" and I think I got it right. I can also share if interested. |
I've got a mostly working implementation I was planning to share in a day or two |
|
Take a look at this one: main...mindnever:zephyr:ws2812-pwm I'm not sure if its good enough for PR. I have tested it with Nucleo H563ZI and there is overlay file for it, but it is also working with F103RE on another proprietary board. |
Looks very reasonable to me. My version, tested so far on stm32g0b1 and stm32wb55rg but will test on a few other targets when I have time: petejohanson@5bda69f (Note: Mine is in a backport to Zephyr v4.1.0, so beware it's built on an older base with this PR's DMA API change cherry-picked in) Compared to yours, mine lacks:
Things I've implemented that don't seem to be included with yours:
I'm have no attachment to my implementation "winning", but it would be nice to have the two above features included if/when you PR your version into Zephyr. I would also suggest you adjust the naming of your driver, since it is STM32 specific, and |



Extends the PWM API with optional API functions for enabling DMA requests triggered by a given PWM channel.
Possible solution for #88670
Tested and verified on NUCLEO-WB55RG development board