Question; How to use other channels than TIMx_CH1 with DMA (+ PWM)? Topic is solved

ChibiOS public support forum for topics related to the STMicroelectronics STM32 family of micro-controllers.

Moderator: RoccoMarco

Post Reply
Engemil
Posts: 37
Joined: Thu Dec 19, 2024 12:50 am
Has thanked: 13 times
Been thanked: 10 times

Question; How to use other channels than TIMx_CH1 with DMA (+ PWM)?

Post by Engemil »

Hi!

I got DMA with PWM working with TIM1_CH1, TIM16_CH1, and I guess all TIMx_CH1. However, I seem to have a problem with utilizing other TIM channels, such as TIM1_CH3. And here I'm not sure if there are steps missing to make it work?

ChibiOS version: Newest (Master branch, commit f3e9979a4896ad6c8cad402fc56543181472f279 from Github)

With the newest chconf.h, halconf.h, and mcuconf.h files.


I imagined it was only necessary to specify with this:

Code: Select all

// Set PA0 to alternate function for TIM16_CH1 (AF2)
palSetPadMode(GPIOA, GPIOA_PIN0, PAL_MODE_ALTERNATE(2));
pwmStart(&PWMD16, &pwm_cfg); // For TIM1 (PA0 for TIM16_CH1)
dma_stream = ... ;
dmaSetRequestSource(dma_stream, 44);  // DMAMUX request 44 for TIM16_CH1 (See RM0490 Reference Manual, Table 49)
dmaStreamSetPeripheral(dma_stream, &TIM16->CCR1);


Here I am showing my PWM+DMA example for STM32C071RB to control a WS2812B LED light:

main.c, under main() I'm showing 3 different tests.

Code: Select all

#include "ch.h"
#include "hal.h"

#define PWM_HI (14)
#define PWM_LO (6)
#define BITS_PER_PIXEL (24)
#define PWM_BUFFER_SIZE (BITS_PER_PIXEL + 1U) // Include 1 extra bit
#define PWM_RESET_BUFFER_SIZE (40U) // 40 * 1.25us = 50us reset time

// Common DMA mode settings (with MINC)
#define DMA_MODE_1 ( \
    STM32_DMA_CR_DIR_M2P /* Memory to peripheral */ \
    | STM32_DMA_CR_MINC /* Increment memory pointer */ \
    | STM32_DMA_CR_PSIZE_HWORD /* Peripheral size 16 bits */ \
    | STM32_DMA_CR_MSIZE_BYTE /* Memory size 8 bits */ \
    | STM32_DMA_CR_TCIE /* Transfer complete interrupt enable */ \
    | STM32_DMA_CR_TEIE /* Transfer error interrupt enable */ \
    | STM32_DMA_CR_PL(0) /* Priority low */ \
)

// Common DMA mode settings (without MINC)
#define DMA_MODE_2 ( \
    STM32_DMA_CR_DIR_M2P /* Memory to peripheral */ \
    | STM32_DMA_CR_PSIZE_HWORD /* Peripheral size 16 bits */ \
    | STM32_DMA_CR_MSIZE_BYTE /* Memory size 8 bits */ \
    | STM32_DMA_CR_TCIE /* Transfer complete interrupt enable */ \
    | STM32_DMA_CR_TEIE /* Transfer error interrupt enable */ \
    | STM32_DMA_CR_PL(0) /* Priority low */ \
)

const uint8_t pwm_zero_buf = 0;
uint8_t pwm_buf[PWM_BUFFER_SIZE] = {0}; // Ensure last value is untouched (always zero).

static const PWMConfig pwm_cfg = {
    .frequency  = 16000000,  // Counter clock frequency for PSC=2
    .period     = 20,        // PWM period in ticks (ARR + 1)
    .callback   = NULL,
    .channels   = {
        {.mode  = PWM_OUTPUT_ACTIVE_HIGH, .callback = NULL},
        {.mode  = PWM_OUTPUT_DISABLED,    .callback = NULL},
        {.mode  = PWM_OUTPUT_DISABLED,    .callback = NULL},
        {.mode  = PWM_OUTPUT_DISABLED,    .callback = NULL}
    },
    .cr2        = STM32_TIM_CR2_CCDS,  // DMA requests on capture/compare events
#if STM32_ADVANCED_DMA
    .bdtr       = 0,
#endif
    .dier       = STM32_TIM_DIER_CC1DE  // Enable DMA on CC1 event
};

static const stm32_dma_stream_t *dma_stream; // Global DMA stream pointer

static volatile bool dma_ready = true;

static void dma_callback(void *p, uint32_t flags) {
    (void)p;
    if (flags & STM32_DMA_ISR_TCIF) {
        dma_ready = true;
    }
    // Handle errors if flags & STM32_DMA_ISR_TEIF, etc.
}

void led_set_RGB(uint8_t r, uint8_t g, uint8_t b) {
    for(int i = 0; i < 8; i++){
        pwm_buf[i] = (g & (1 << i)) ? PWM_HI : PWM_LO;
        pwm_buf[i + 8] = (r & (1 << i)) ? PWM_HI : PWM_LO;
        pwm_buf[i + 16] = (b & (1 << i)) ? PWM_HI : PWM_LO;
    }
    // pwm_buf[24] remains 0
}

void led_reset(void){
    while (!dma_ready) {
        chThdSleepMilliseconds(1);  // Wait for previous DMA to complete
    }
    dma_ready = false;
    dmaStreamDisable(dma_stream);
    dmaStreamSetMode(dma_stream, DMA_MODE_2); // No MINC for repeated zero
    dmaStreamSetMemory0(dma_stream, &pwm_zero_buf);
    //dmaStreamSetMemory0(dma_stream, (uint8_t*)PWM_ZERO);
    dmaStreamSetTransactionSize(dma_stream, PWM_RESET_BUFFER_SIZE);
    dmaStreamEnable(dma_stream);
}

void led_render(void) {
    while (!dma_ready) {
        chThdSleepMilliseconds(1);  // Wait for previous DMA to complete
    }
    dma_ready = false;
    dmaStreamDisable(dma_stream);
    dmaStreamSetMode(dma_stream, DMA_MODE_1);
    dmaStreamSetMemory0(dma_stream, pwm_buf);
    dmaStreamSetTransactionSize(dma_stream, PWM_BUFFER_SIZE);
    dmaStreamEnable(dma_stream);
}

int main(void) {

    halInit();
    chSysInit();

    // TEST 1
    //palSetPadMode(GPIOA, GPIOA_PIN0, PAL_MODE_ALTERNATE(2)); // WORKS (TIM16_CH1)
    // TEST 2
    //palSetPadMode(GPIOC, GPIOC_PIN8, PAL_MODE_ALTERNATE(2)); // WORKS (TIM1_CH1)
    // TEST 3
    palSetPadMode(GPIOC, GPIOC_PIN10, PAL_MODE_ALTERNATE(2)); // NOT WORKING

    // Start PWM
    // TEST 1
    //pwmStart(&PWMD16, &pwm_cfg); // For TIM16 (PA0 for TIM16_CH1)
    // TEST 2
    pwmStart(&PWMD1, &pwm_cfg); // For TIM1 (PC8 for TIM1_CH1)
    // TEST 3
    //pwmStart(&PWMD1, &pwm_cfg); // For TIM1 (PC10 for TIM1_CH3)

    // Setup DMA
    dma_stream = dmaStreamAlloc(STM32_DMA_STREAM_ID(1U, 1U), 0, (stm32_dmaisr_t)dma_callback, NULL);
   
    // TEST 1
    //dmaSetRequestSource(dma_stream, 44);  // DMAMUX request 44 for TIM16_CH1 (See RM0490 Reference Manual, Table 49)
    //dmaStreamSetPeripheral(dma_stream, &TIM16->CCR1);
    // TEST 21
    dmaSetRequestSource(dma_stream, 20);  // DMAMUX request 20 for TIM1_CH1 (See RM0490 Reference Manual, Table 49)
    dmaStreamSetPeripheral(dma_stream, &TIM1->CCR1);
    // TEST 3
    //dmaSetRequestSource(dma_stream, 22);  // DMAMUX request 22 for TIM1_CH3 (See RM0490 Reference Manual, Table 49)
    //dmaStreamSetPeripheral(dma_stream, &TIM1->CCR3);
   
    //dmaStreamSetMode(dma_stream, DMA_MODE_1); // Not needed here
   
    while (true) {
        led_set_RGB(0xFF, 0x00, 0x00);
        led_reset();
        led_render();
        chThdSleepMilliseconds(500);
        led_set_RGB(0x00, 0xFF, 0x00);
        led_reset();
        led_render();
        chThdSleepMilliseconds(500);
        led_set_RGB(0x00, 0x00, 0xFF);
        led_reset();
        led_render();
        chThdSleepMilliseconds(500);
    }
    return 0;
}


halconf.h, specific changes:

Code: Select all

...
#define HAL_USE_PWM                         TRUE
...


mcuconf.h, specific changes:

Code: Select all

...
#define STM32_PWM_USE_TIM1                  TRUE // For Test 2 and 3
//#define STM32_PWM_USE_TIM16                  TRUE // For test 1
...
/*
 * DMA driver system settings.
 */
#define STM32_DMA_REQUIRED                  TRUE // Added for DMA support
...
User avatar
Giovanni
Site Admin
Posts: 14891
Joined: Wed May 27, 2009 8:48 am
Has thanked: 1202 times
Been thanked: 996 times

Re: Question; How to use other channels than TIMx_CH1 with DMA (+ PWM)?

Post by Giovanni »

Hi,

Have you enabled DMA requests in the TIM_DIER register? HAL drivers do nothing about this, it is all about configuration.

Giovanni
Engemil
Posts: 37
Joined: Thu Dec 19, 2024 12:50 am
Has thanked: 13 times
Been thanked: 10 times

Re: Question; How to use other channels than TIMx_CH1 with DMA (+ PWM)?

Post by Engemil »

Hi Giovanni!

SOLVED! Thanks for the clarifications! It was just as simple as enabling the DMA request in TIM_DIER. The CCxDE (CC1DE, CC2DE, CC3DE, CC4DE) needs to be enabled (0x01) for each correlating channel to work (TIM1_CH1, _CH2, ...).

I'll leave a note here for people to see:

I checked TIM1_DIER register and saw that CC1DE was 0x01, while the others where not.

A quick test by writing manually (with Cortex-Debug extension under xperipherals during debugging) during a breakpoint, I wrote 0x01 to CC3DE, and it worked for TIM1_CH3.

Image

That is when I realized that I'm actually setting CC1DE in my pwm_cfg. Now to make it work for TIM1_CH3 I have to do two things in pwm_cfg:

- Set .dier to STM32_TIM_DIER_CC3DE
- Set 3rd channel mode (corresponding to channel 3 in TIM1) to PWM_OUTPUT_ACTIVE_HIGH

As following:

Code: Select all

static const PWMConfig pwm_cfg = {
    .frequency  = 16000000,  // Counter clock frequency for PSC=2
    .period     = 20,        // PWM period in ticks (ARR + 1)
    .callback   = NULL,
    .channels   = {
        {.mode  = PWM_OUTPUT_DISABLED, .callback = NULL},
        {.mode  = PWM_OUTPUT_DISABLED,    .callback = NULL},
        {.mode  = PWM_OUTPUT_ACTIVE_HIGH,    .callback = NULL},
        {.mode  = PWM_OUTPUT_DISABLED,    .callback = NULL}
    },
    .cr2        = STM32_TIM_CR2_CCDS,  // DMA requests on capture/compare events
#if STM32_ADVANCED_DMA
    .bdtr       = 0,
#endif
    .dier       = STM32_TIM_DIER_CC3DE  // Enable DMA on CC3 event
};
Post Reply