Hardware timer + DMA: mbed os 5.15 vs gcc4mbed

Hi everyone!
I’d like to drive a set of ws2812 LEDs with hardware timer and DMA. I have a code written in Mbed Studio (with mbed os version 5) that works fine, but - for historical reasons - I have to get this work with gcc4mbed, and that’s the point where I need help. The program runs well (led2 blinks), except that I can’t recognize anything on PB_11, it’s permanently on L level.
. What can be the difference between the mbed os version used by gcc4mbed (5.4 I guess?) and 5.15 that causes that? Is there something initialised with this pin that I have to deinit or something like that?
The board is a Nucleo F767ZI.
main.cpp (simplified)

#include <mbed.h>
#include "ws2812b-dma-driver.h"

DigitalOut led2(LED2);

InterruptIn btn(BUTTON1);

ws2812dmaWrapper ws;

volatile int prg_pntr = 0;
volatile int l_prg_pntr = 0;

int main(){
    ws2812b_init();
    
    Color_t color;
    color.b = 255;
    color.g = 150;
    color.r = 0;
    
    while(1){
        led2 = !led2;
        setAll_GRB(&color);
        drawFrame();
        wait_ms(500);
        clearAll();
        drawFrame();
        wait_ms(500);
    
    }
}

stm32f7xx_hal_msp.c

#include "ws2812b-dma-driver.h"

extern DMA_HandleTypeDef hdma_timx_chn;
      
/**
* @brief TIM_Base MSP Initialization
* This function configures the hardware resources used in this example
* @param htim_base: TIM_Base handle pointer
* @retval None
*/
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base)
{
  if(htim_base->Instance==TIMx)
  {
    /* Peripheral clock enable */
    TIMx_CLK_ENABLE();
  
    hdma_timx_chn.Instance = TIMx_CC1_DMA_STREAM;
    hdma_timx_chn.Init.Channel = TIMx_CC1_DMA_CHANNEL;
    hdma_timx_chn.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_timx_chn.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_timx_chn.Init.MemInc = DMA_MINC_ENABLE;
    if (TIMx == TIM2){
        hdma_timx_chn.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
        hdma_timx_chn.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
    }
    else {
        hdma_timx_chn.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
        hdma_timx_chn.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    }
    hdma_timx_chn.Init.Mode = DMA_NORMAL;
#if WS2812B_FIFO_MODE
    hdma_timx_chn.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
#else
    hdma_timx_chn.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
#endif
    hdma_timx_chn.Init.Priority = DMA_PRIORITY_HIGH;
    hdma_timx_chn.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
    hdma_timx_chn.Init.MemBurst = DMA_MBURST_INC8;
    hdma_timx_chn.Init.PeriphBurst = DMA_PBURST_SINGLE;
    if (HAL_DMA_Init(&hdma_timx_chn) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(htim_base,hdma[TIM_DMA_HANDLE_ID],hdma_timx_chn);
    #ifdef TIM_DMA_HANDLE_ID2
    __HAL_LINKDMA(htim_base,hdma[TIM_DMA_HANDLE_ID2],hdma_timx_chn);
    #endif
  }

}

void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(htim->Instance==TIMx)
  {
  
    TIMx_GPIO_CLK_ENABLE();
    /**TIM2 GPIO Configuration    
    PA0-WKUP     ------> TIM2_CH1 
    */
    GPIO_InitStruct.Pin = TIMx_GPIO_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF_TIMx;
    HAL_GPIO_Init(TIMx_GPIO_PORT, &GPIO_InitStruct);
  }

}

void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* htim_base)
{
  if(htim_base->Instance==TIMx)
  {
    /* Peripheral clock disable */
    __HAL_RCC_TIM1_CLK_DISABLE();

    /* TIM1 DMA DeInit */
    HAL_DMA_DeInit(htim_base->hdma[TIM_DMA_HANDLE_ID]);
  }
}

ws2812b-dma-driver.h

#ifndef __WS2812B_DMA_DRIVER_H__
#define __WS2812B_DMA_DRIVER_H__

#define WS2812B_LEDS_NUM 24
#define WS2812_TIM2CH4 

#ifdef __cplusplus
extern "C" {
#endif

#include "stm32f7xx_hal.h"
#include "math.h"

#ifdef WS2812_TIM2CH4
    /* Definition of TIM instance */
    #define TIMx                             	TIM2

    #define TIMER_PRESCALER                     (1)
    #define TIMER_CLOCK_FREQ	                (108000000)

    /* Definition for TIMx clock resources */
    #define TIMx_CLK_ENABLE                  	__HAL_RCC_TIM2_CLK_ENABLE
    #define TIMx_CLK_DISABLE                  	__HAL_RCC_TIM2_CLK_DISABLE
    #define DMAx_CLK_ENABLE                  	__HAL_RCC_DMA1_CLK_ENABLE

    /* Definition for TIMx Pins */
    #define TIMx_GPIO_CLK_ENABLE    	        __HAL_RCC_GPIOB_CLK_ENABLE
    #define TIMx_GPIO_PORT          	        GPIOB

    #define TIMx_GPIO_PIN                	    GPIO_PIN_11
    #define GPIO_AF_TIMx                     	GPIO_AF1_TIM2

    /* Definition for TIMx channels */
    #define TIMx_CHANNEL                        TIM_CHANNEL_4
    #define TIM_DMA_HANDLE_ID                   TIM_DMA_ID_CC4
    #define TIM_DMA_HANDLE_ID2                  TIM_DMA_ID_CC2

    /* Definition for TIMx's DMA */
    #define TIMx_CC1_DMA_CHANNEL             	DMA_CHANNEL_3
    #define TIMx_CC1_DMA_STREAM               	DMA1_Stream6

    /* Definition for DMAx's NVIC */
    #define TIMx_DMA_IRQn                    	DMA1_Stream6_IRQn
    #define TIMx_DMA_IRQHandler              	DMA1_Stream6_IRQHandler
#endif

/* ws2812b defines */ 
#define WS2812_FREQ			            (800000)        // it is fixed: WS2812 require 800kHz
#define WORDS_PER_LED                   (24)
#define TIMER_PERIOD		            (TIMER_CLOCK_FREQ / WS2812_FREQ)
#ifdef WS2812B
    #define WS2812_TH1                      (round(TIMER_PERIOD * 18 / 25))
    #define WS2812_TH0                      (round(TIMER_PERIOD * 7 / 25))
#else
    #define WS2812_TH1                      (round(TIMER_PERIOD * 2 / 3))
    #define WS2812_TH0                      (round(TIMER_PERIOD * 1 / 3))
#endif
#define NUM_LEDS                        (WS2812B_LEDS_NUM)
#define RESET_SLOT                      (50)
#define LED_BUFFER_SIZE                 ((NUM_LEDS * WORDS_PER_LED) + RESET_SLOT * 2)

typedef struct Color
{
	uint8_t r; // red
	uint8_t g; // green
	uint8_t b; // blue
} Color_t;

/* ws2812b-dma-driver API */ 

void ws2812b_init(void);
void drawFrame(void);
void clearAll(void);
void getPixelColor(int px_index, Color_t * px_color);
void setPixel_GRB(const Color_t * Color, int px_index);
void setAll_GRB(const Color_t * color);
void setRange_GRB(const Color_t * color, int start_px_index, int len);
void setColorBrightness(const Color_t * in, Color_t * out, float brightness);

/* ******************** */ 

void TIMx_DMA_IRQHandler(void);
void HAL_TIM_MspPostInit(TIM_HandleTypeDef *htim);
void Error_Handler(void);

#ifdef __cplusplus
}
#endif

#endif

ws2812b-dma-driver.c

#include "ws2812b-dma-driver.h"

TIM_HandleTypeDef htimx;
DMA_HandleTypeDef hdma_timx_chn;
//static uint32_t * ws2812b_data;
static uint32_t ws2812b_data[LED_BUFFER_SIZE];


static void MX_DMA_Init(void);
static void MX_TIMx_Init(void);


void ws2812b_init(void)
{
    //ws2812b_data = (uint32_t *)calloc(LED_BUFFER_SIZE, sizeof(uint32_t)); 
    for (int i = 0; i < LED_BUFFER_SIZE; i++){
        ws2812b_data[i] = 0;
    }
    assert_param(data);
    __disable_irq();
    MX_DMA_Init();
    MX_TIMx_Init();
    __enable_irq();
}

void drawFrame(void)
{
	HAL_TIM_PWM_Start_DMA(&htimx, TIMx_CHANNEL, (uint32_t *)ws2812b_data, LED_BUFFER_SIZE);
}

void setPixel_GRB(const Color_t * color, int px_index)
{

	int index = px_index * WORDS_PER_LED + RESET_SLOT;
    for(int i=0;i<8;i++)
    {
        // high bit send at first
    	ws2812b_data[index+i] = (color->g >> (7-i)) & 1 ? WS2812_TH1 : WS2812_TH0;
    	ws2812b_data[index+i+8] = (color->r >> (7-i)) & 1 ? WS2812_TH1 : WS2812_TH0;
    	ws2812b_data[index+i+16] = (color->b >> (7-i)) & 1 ? WS2812_TH1 : WS2812_TH0;
    }
}

void setAll_GRB(const Color_t * color)
{
    for(int i=0;i<NUM_LEDS;i++)
    {
    	setPixel_GRB(color, i);
    }
}

void getPixelColor(int px_index, Color_t * px_color){
	int index = px_index * WORDS_PER_LED;
    for(int i=0;i<8;i++)
    {
    	px_color->g |= (ws2812b_data[index+i] == WS2812_TH1 ? 1 : 0) << (7-i);
    	px_color->r |= (ws2812b_data[index+i+8] == WS2812_TH1 ? 1 : 0) << (7-i);
    	px_color->b |= (ws2812b_data[index+i+16] == WS2812_TH1 ? 1 : 0) << (7-i);
    }
}

void setRange_GRB(const Color_t * color, int start_px_index, int len)
{
    for(int i=start_px_index;i<start_px_index+len;i++)
    {
    	setPixel_GRB(color, i);
    }
}

void clearAll(void)
{
	for(int i = RESET_SLOT; i<(LED_BUFFER_SIZE - RESET_SLOT); i++)
		ws2812b_data[i] = WS2812_TH0;
}

void setColorBrightness(const Color_t * in, Color_t * out, float brightness)
{
    out->r = brightness * in->r;
    out->g = brightness * in->g;
    out->b = brightness * in->b;
}

void TIMx_DMA_IRQHandler(void)
{
  HAL_DMA_IRQHandler(&hdma_timx_chn);
}
void Error_Handler(void){}

void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
	HAL_TIM_PWM_Stop_DMA(htim, TIMx_CHANNEL);
}

static void MX_TIMx_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};

  htimx.Instance = TIMx;
  htimx.Init.Prescaler = (uint32_t)TIMER_PRESCALER-1;
  htimx.Init.CounterMode = TIM_COUNTERMODE_UP;
  htimx.Init.Period = (uint32_t)TIMER_PERIOD - 1;
  htimx.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htimx.Init.RepetitionCounter = 0;

  htimx.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htimx) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htimx, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htimx) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htimx, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  sConfigOC.OCIdleState=TIM_OCIDLESTATE_RESET;
  if (HAL_TIM_PWM_ConfigChannel(&htimx, &sConfigOC, TIMx_CHANNEL) != HAL_OK)
  {
    Error_Handler();
  }

  sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
  sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
  sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
  sBreakDeadTimeConfig.DeadTime = 0;
  sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
  sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
  sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
  if (HAL_TIMEx_ConfigBreakDeadTime(&htimx, &sBreakDeadTimeConfig) != HAL_OK)
  {
    Error_Handler();
  }
  HAL_TIM_MspPostInit(&htimx);
}

static void MX_DMA_Init(void) 
{

  /* DMA controller clock enable */
  DMAx_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA1_Stream5_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(TIMx_DMA_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(TIMx_DMA_IRQn);

}

Hello Álmos,

I have a code written in Mbed Studio (with mbed os version 5) that works fine

Does it correctly drive the ws2812 LEDs connected to pin PB_11 using hardware timer and DMA ?

Yes, it does.
Meanwhile I’ve “solved” the problem about an hour ago, and now it works with gcc4mbed too.
I had to wipe this part out from MX_TIMx_Init():

TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};

sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
sBreakDeadTimeConfig.DeadTime = 0;
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
if (HAL_TIMEx_ConfigBreakDeadTime(&htimx, &sBreakDeadTimeConfig) != HAL_OK)
{
	Error_Handler();
}

I am not very familiar with HAL, hence I don’t exactly know why was this necessary to work under gcc4mbed, so it’s time for me to dig deeper :slight_smile:

I’m glad you made it work. Unfortunately, I have never used the gcc4mbed support package so I don’t know either why such initialization is needed. When I need DMA support in Mbed for STM targets I usually let the STM32CubeIDE device configuration tool to generate the code and then I move/include it into the Mbed project.