cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F7 DMA Memory to GPIO by Timer, problem...

Aurelien Robert
Associate III
Posted on June 21, 2017 at 21:51

Hello,

I'm running a STM32F767 on NucleoF767ZI board, with SW4STM32 IDE.

I'm coding a procedure that transfers with DMA a lot of data (24576 x 16bits words) from internal memory to GPIOE, cadenced by TIMER3 Output compare Channel 3.

TIMER3 output compare module is used in PWM mode, channels 1 and also used, and the three channels (1,2,3) also toggle output GPIO pins (on port B).

I mainly set my code by copying the AN4666 app note (made for STM32F4/L4 device, but I don't see any reason for STM32F7 would not support it).

This is my code :

TIM_HandleTypeDef TimHandle;

TIM_OC_InitTypeDef sConfig;

uint16_t output_buf[24576] __attribute__((section ('.testSRAM1Location')));

int main(void)

{

SCB_EnableICache(); // for partial enable

//SCB_EnableDCache(); // for partial enable

HAL_Init();

SystemClock_Config200MHz();

BSP_LED_Init(LED1);

BSP_LED_Init(LED2);

BSP_LED_Init(LED3);

__HAL_RCC_TIM3_CLK_ENABLE();

TimHandle.Instance = TIM3;

TimHandle.Init.Period = 1250; // 12.5µs cycle at 100MHz

TimHandle.Init.Prescaler = 0;

TimHandle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // timer period 10ns (100MHz)

TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP;

TimHandle.Init.RepetitionCounter = 0;

TimHandle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;

if (HAL_TIM_PWM_Init(&TimHandle) != HAL_OK) Error_Handler();

sConfig.OCMode = TIM_OCMODE_PWM1;

sConfig.OCPolarity = TIM_OCPOLARITY_HIGH;

sConfig.OCFastMode = TIM_OCFAST_DISABLE;

sConfig.OCNPolarity = TIM_OCNPOLARITY_LOW;

sConfig.OCNIdleState = TIM_OCNIDLESTATE_RESET;

sConfig.OCIdleState = TIM_OCIDLESTATE_RESET;

// Set the pulse value for channel 1

sConfig.Pulse = 250;

if (HAL_TIM_PWM_ConfigChannel(&TimHandle, &sConfig, TIM_CHANNEL_1) != HAL_OK) Error_Handler();

// Set the pulse value for channel 2

sConfig.Pulse = 600;

if (HAL_TIM_PWM_ConfigChannel(&TimHandle, &sConfig, TIM_CHANNEL_2) != HAL_OK) Error_Handler();

// Set the pulse value for channel 3

sConfig.Pulse = 670;

if (HAL_TIM_PWM_ConfigChannel(&TimHandle, &sConfig, TIM_CHANNEL_3) != HAL_OK) Error_Handler();

if (HAL_TIM_PWM_Start(&TimHandle, TIM_CHANNEL_1) != HAL_OK) Error_Handler();

if (HAL_TIM_PWM_Start(&TimHandle, TIM_CHANNEL_2) != HAL_OK) Error_Handler();

if (HAL_TIM_PWM_Start(&TimHandle, TIM_CHANNEL_3) != HAL_OK) Error_Handler();

TimHandle.hdma[TIM_DMA_ID_CC3]->XferCpltCallback = data_tramsmitted_handler;

TimHandle.hdma[TIM_DMA_ID_CC3]->XferErrorCallback = transmit_error_handler;

// Configure the GPIOE for DMA operation

__HAL_RCC_GPIOE_CLK_ENABLE();

HAL_GPIO_WritePin(GPIOE, GPIO_PIN_All, GPIO_PIN_RESET);

GPIO_InitStruct.Pin = GPIO_PIN_All;

GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;

GPIO_InitStruct.Pull = GPIO_NOPULL;

GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;

HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

// Preload data in buffer for DMA example

for (i=0 ; i<24576 ; i++) output_buf[i] = (i+1);

while (1)

{

HAL_Delay(1000);

SCB_CleanDCache(); // clean the cache to be sure DMA get correct data

if (HAL_DMA_Start_IT(TimHandle.hdma[TIM_DMA_ID_CC3], (uint32_t)&output_buf, (GPIOE_BASE + 0x14), 24576) != HAL_OK) Error_Handler();

__HAL_TIM_ENABLE_DMA(&TimHandle, TIM_DMA_CC3);

}

}

void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)

{

GPIO_InitTypeDef GPIO_InitStruct;

static DMA_HandleTypeDef hdma_tim;

/* Enable peripherals and GPIO Clocks #################################*/

/* TIM3 Peripheral clock enable */

__HAL_RCC_TIM3_CLK_ENABLE();

/* Enable all GPIO Channels Clock requested */

__HAL_RCC_GPIOB_CLK_ENABLE();

/* Enable DMA clock */

__HAL_RCC_DMA1_CLK_ENABLE();

/* Configure PB.04 (TIM3_Channel1), PB.05 (TIM3_Channel2), PB.00 (TIM3_Channel3) in output, push-pull, alternate function mode */

/* Common configuration for all channels */

GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;

GPIO_InitStruct.Pull = GPIO_NOPULL;

GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;

GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;

GPIO_InitStruct.Pin = GPIO_PIN_4;

HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;

GPIO_InitStruct.Pin = GPIO_PIN_5;

HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;

GPIO_InitStruct.Pin = GPIO_PIN_0;

HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

/* Configure DMA */

/* Set the parameters to be configured */

hdma_tim.Instance = DMA1_Stream7;

hdma_tim.Init.Channel = DMA_CHANNEL_5;

hdma_tim.Init.Direction = DMA_MEMORY_TO_PERIPH;

hdma_tim.Init.PeriphInc = DMA_PINC_DISABLE;

hdma_tim.Init.MemInc = DMA_MINC_ENABLE;

hdma_tim.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;

hdma_tim.Init.MemDataAlignment = DMA_PDATAALIGN_HALFWORD;

hdma_tim.Init.Mode = DMA_NORMAL;

hdma_tim.Init.Priority = DMA_PRIORITY_VERY_HIGH;

hdma_tim.Init.FIFOMode = DMA_FIFOMODE_DISABLE;

hdma_tim.Init.MemBurst = DMA_MBURST_SINGLE;

hdma_tim.Init.PeriphBurst = DMA_PBURST_SINGLE;

/* Link hdma_tim to hdma[CC3] */

__HAL_LINKDMA(htim, hdma[TIM_DMA_ID_CC3], hdma_tim);

/* Initialize TIMx DMA handle */

HAL_DMA_Init(htim->hdma[TIM_DMA_ID_CC3]);

HAL_NVIC_SetPriority(DMA1_Stream7_IRQn, 1, 1);

HAL_NVIC_EnableIRQ(DMA1_Stream7_IRQn);

}

static void SystemClock_Config200MHz(void)

{

RCC_ClkInitTypeDef RCC_ClkInitStruct;

RCC_OscInitTypeDef RCC_OscInitStruct;

/* Enable HSE Oscillator and activate PLL with HSE as source */

RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;

RCC_OscInitStruct.HSEState = RCC_HSE_BYPASS;

RCC_OscInitStruct.HSIState = RCC_HSI_OFF;

RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;

RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;

RCC_OscInitStruct.PLL.PLLM = 8;

RCC_OscInitStruct.PLL.PLLN = 400;

RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;

RCC_OscInitStruct.PLL.PLLQ = 9;

RCC_OscInitStruct.PLL.PLLR = 7;

if(HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)

{

while(1) {};

}

/* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2

clocks dividers */

RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);

RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;

RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;

RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;

RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

if(HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_7) != HAL_OK)

{

while(1) {};

}

}

I did not post the error handlers here because they only consist in toggling a LED and while(1)...

When I enable the DMA and the TMR3:CC3 request, I don't see anything change on the output port and immediately after enabling TMR3:CC3 DMA request a transfert error interrupt rises.

When the error interrupt is entered, the data counter is decremented (0x5FFFF) but the memory address is not incremented ; the EN bit is automatically cleared by hardware.

When running in direct mode or in fifo mode with no burst set (DMA_PBURST_SINGLE), I get error code 1 (TE). When running in fifo mode with half full trigger and 4 burst set (DMA_MBURST_INC4) I get error code 2 (FE).

I tried with the buffer in DTCM memory, in SRAM1 memory, with data cache enabled and with data cable disabled.

Of course I checked the PWM outputs before coding DMA, and it still working well on the three outputs with correct freq et d/c.

Thank you for your ideas

Aurelien
9 REPLIES 9
Aurelien Robert
Associate III
Posted on June 21, 2017 at 22:37

I just read now, after one full afternoon reading doc, that the DMA1 controller does not have connection for AHB bux matrix for the peripheral destination, so it cannot reach the GPIO memory zone. I should use the DMA2 (and either TIM1 or TIM8) to send data on GPIO address...

I will check it tomorrow...

Posted on June 22, 2017 at 00:09

I don't speak the Cube gobbledygook and can't see how do you set the peripheral address, but if it's GPIO register, the DMA1 can't use that as the peripheral address. You can use only APB1 peripherals' addresses for DMA1 peripheral. GPIOs are on AHB1. Look at the bus/matrix interconnection scheme, it tends to be the first figure in the RMs.

Use DMA2 (and respective timer of course).

When the error interrupt is entered, the data counter is decremented (0x5FFFF) but the memory address is not incremented

You won't see the address registers to change ever. All address registers and the NDTR are in fact double registers, but while NDTR's 'current' or 'changing' is exposed for reading, it's the other way round for the address registers. Please note that I am not responsible for ST's design choices, I'm just explaining the status quo.

JW

Aurelien Robert
Associate III
Posted on June 22, 2017 at 12:20

OK, it's better, now that works.

I tried in fifo mode with single burst, no problem.

But when I select inc4_burst (fifo level at half full, I'm using half-word data), I get a fifo error interrupt at the end of the full payload transfert (remaining data = 0).

Precisely, that's what I get : I enable the DMA (timer request disabled), then I pause debug and I see that the fifo level is between 0 and 1/4 (not marked as empty). I enable the timer request, then the full data is sent (NDT=0), and at the end I get a FIFO error (fifo level is marked as empty) ; the DMA is also automatically disabled (EN=0).

And after that, I re-enable the DMA (with timer request disabled during init), and I get immediately a fifo error interrupt (timer request still disabled). When executing the interrupt, the register shows that the fifo level is empty and obviously no data has been sent.

If i set the fifo burst equal to single, I don't have any FIFO error and the stream can be repeated as many times I need without generating errors...

Posted on June 22, 2017 at 14:16

I mean the situation when you have burst set .

Posted on June 22, 2017 at 13:51

Post the content of the given DMA stream's registers.

JW

Posted on June 22, 2017 at 14:07

0690X00000607UDQAY.png
Posted on June 22, 2017 at 14:44

My mistake, I found the bug...(because when I looked another time to the screenshot I posted, the DMDIS was cleared...)

I used the value 'DMA_PDATAALIGN_HALFWORD' for the MemDataAlignment parameter, instead of using 'DMA_MDATAALIGN_HALFWORD'. The HAL init was returning an error during parameter assertion, I was not checking it before. Now it works very well....

Thank you

Posted on June 22, 2017 at 14:55

Can you for others please post your final solution?

Thanks,

JW

Posted on June 22, 2017 at 15:16

This is the same code than above, with all TMR3 occurences replaced by TMR8 (update prescaler value to 1 instead of 0 because on my config APB2 clock is twice fast than APB1 clock) , and DMA1/stream replaced by DMA2/stream.

The DMA init becomes :

hdma_tim.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;

hdma_tim.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;

hdma_tim.Init.Mode = DMA_NORMAL;

hdma_tim.Init.Priority = DMA_PRIORITY_VERY_HIGH;

hdma_tim.Init.FIFOMode = DMA_FIFOMODE_ENABLE;

hdma_tim.Init.MemBurst = DMA_MBURST_INC4;

hdma_tim.Init.PeriphBurst = DMA_PBURST_SINGLE;

I also added __HAL_TIM_DISABLE_DMA(&TimHandle, TIM_DMA_CC3); before each restart of DMA.

Of course this is only an evaluation code, I need to precisely synchronise the timer restart, and connect the timer to another one to generate a end of transfert signal on GPIO (like performed in AN4666 STM32F4 demo). But concept is validated.