STM32F4 DMA Mem->GPIO triggered by timer - Page 1
Forum section: electronics / general

author: MattHollands
author_rank: Frequent Contributor
timestamp: April 17, 2022, 03:16:58 am
content: Hi all,I am trying to generate an 8-bit bit pattern with approximately an 4MHz update rate using an STM32F446 microcontroller. My general idea is to configure a timer to trigger a DMA update from memory to GPIO in order to generate a certain bit pattern.Creating the timer is easy, but I am unable to actually get the DMA to trigger. I have been following a number of sources online, in particular this YouTube video () which is accompanied by this GitHub codehttps://github.com/mnemocron/STM32_PatternDriver/blob/main/Core/Src/main.cI think I have matched his code almost exactly (other than using a different chip, different timer and different GPIOs) but I cannot get the DMA to actually run. Here is my code and see attached images for STM32Cube configuration. The expected outcome is a square wave on PA8 (generated by the OC comparison) and then a similar square wave on PA0 generated by the DMA writes to the GPIOA->BSRR register.I've tried all sorts of things, and to be honest I don't quite understand what some of this code is meant to do (for example why he is using the timer update to trigger DMA instead of the OC, or why he calls both HAL_TIM_Base_Start and HAL_TIM_OC_Start). At this point I've tried a bunch of variations and different online code examples, but none of them actually seem to trigger the DMA. I can see the timer is running and generating the pulse on PA8, but nothing is happening on PA0.Any fresh eyes or experience very much appreciated!Thanks,MattCode:int main(void){/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_TIM1_Init();MX_DMA_Init();/* USER CODE BEGIN 2 */// GPIOA->BSRR = 0x00000001;uint32_t pixelclock = {0x00000001, 0x00010000, 0x00000001, 0x00010000 };HAL_DMA_Start(&hdma_tim1_up, (uint32_t)pixelclock, (uint32_t)&(GPIOA->BSRR), 4);HAL_TIM_Base_Start(&htim1);HAL_TIM_OC_Start(&htim1, TIM_CHANNEL_1);TIM1->DIER |= (1 << 8);/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */HAL_Delay(500);//HAL_GPIO_TogglePin(GPIOA, 1);}/* USER CODE END 3 */}

author: pcprogrammer
author_rank: Super Contributor
timestamp: April 17, 2022, 04:51:09 am
content: Forget the HAL shit and use direct setting of the peripheral registers.Take a look at the code in this repositoryhttps://github.com/pecostm32/STM32F303_Sine_Square_GeneratorIt is using two DMA channels controlled by timers. One is for making a sine by writing data to a DAC and the other one is making square waves on GPIO pins by writing to a port register.For it all to work the GPIO pins have to be in the same port. The peripherals between the families are not to different. Just get the reference and programming manuals from herehttps://www.st.com/en/microcontrollers-microprocessors/stm32f446.html#documentation

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 17, 2022, 01:54:08 pm
content: Use the timer Update DMA, everytime the timer updates, it triggers a DMA transfer.https://www.cnblogs.com/shangdawei/p/4753284.htmlSomething like:Code:HAL_DMA_Start(&hdma_tim3_up, (uint32_t)SourceBuffer, (uint32_t)&GPIOA->ODR, bufferElements);__HAL_TIM_ENABLE_DMA(&htim3, TIM_DMA_UPDATE);HAL_TIM_Base_Start(&htim3);Check thisrepofor CubeMX config.Edit: I revisited this, for some reason nothing worked...After a lot of head scratching, I remembered that in STM32F4 series (Most if not all),only DMA2 can make transfers from memory to memory(Memory = "Anywhere").If you try to use DMA1, you'll get transfer error flag, because it can only make transfers between memory and APB1 peripherals.Only one DMA1 port can access memory, while the other can only access APB1 peripherals.This often means only Timer 1 can be used for DMA Mem to GPIO transfers, at least in F4 series!

author: wek
author_rank: Frequent Contributor
timestamp: April 17, 2022, 07:14:01 pm
content: Read out and check/post content of TIM, DMA and relevant GPIO registers.JW

author: MattHollands
author_rank: Frequent Contributor
timestamp: April 18, 2022, 03:18:43 am
content: Thanks, for all the input - I think I cracked it!Quote from: pcprogrammer on April 17, 2022, 04:51:09 amI get the sentiment but I prefer to use the HAL where possible since it makes the code a lot more understandable (except when it doesn't) Nonetheless, your link was helpful when it came to debugging everything - thank you.Quote from: DavidAlfa on April 17, 2022, 01:54:08 pmI think this function specifically allows you to write to the timer peripheral only. The destination address you provide is referenced to the timer address.Quote from: wek on April 17, 2022, 07:14:01 pmThis is what I did and I this helped me to find the issue.Basically, I followed section 9.3.18 "Stream configuration procedure" of the STM32F446 reference manual and this showed me the registers I need to set to make it work. Something like this worked for me:Code:uint32_t pixelclock = {0x00000001, 0x00010000, 0x00000001, 0x00010000 };__HAL_RCC_DMA2_CLK_ENABLE();DMA2_Stream1->PAR = (uint32_t)&(GPIOA->BSRR); // Set peripheral addressDMA2_Stream1->M0AR = (uint32_t) pixelclock; // Set memory addressDMA2_Stream1->NDTR = 4; // set number of bytes to transferDMA2_Stream1->CR |= (6 << DMA_SxCR_CHSEL_Pos); // enable channel 6 (TIM1_CH1)DMA2_Stream1->CR |= (2 << DMA_SxCR_PL_Pos); // set to high priority// FIFO should be disabled by defaultDMA2_Stream1->CR |= (1 << DMA_SxCR_DIR_Pos); //set memory-to-peripheralDMA2_Stream1->CR |= (1 << DMA_SxCR_MINC_Pos); //Incremement memory address// Assume PINC is 0//Assume burst is set to single transferDMA2_Stream1->CR |= (2 << DMA_SxCR_MSIZE_Pos); // Set memory data width to a wordDMA2_Stream1->CR |= (2 << DMA_SxCR_PSIZE_Pos); // Set peripheral data width to a wordDMA2_Stream1->CR |= (1 << DMA_SxCR_CIRC_Pos); // Set to circular mode// Assume DBM is zeroDMA2_Stream1->CR |= (1 << DMA_SxCR_EN_Pos); //enable DMAI then compared the CR register to the value when I used the HAL code in my original post and I found that actually it only wrote a couple of the bits, but not enough to actually make the DMA run. I then stumbled across the "HAL_DMA_Init" function that basically does the exact code above!So now I have a working implementation that looks like:Code:int main(void){/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_TIM1_Init();MX_DMA_Init();/* USER CODE BEGIN 2 */uint32_t pixelclock = {0x00000001, 0x00010000, 0x00000001, 0x00010000 };HAL_DMA_Init(&hdma_tim1_ch1);HAL_DMA_RegisterCallback(&hdma_tim1_ch1, HAL_DMA_XFER_CPLT_CB_ID, DMA_XFER_CPLT);HAL_DMA_Start_IT(&hdma_tim1_ch1, (uint32_t)pixelclock, (uint32_t)&(GPIOA->BSRR), 4);HAL_TIM_OC_Start(&htim1, TIM_CHANNEL_1);TIM1->DIER |= (1 << 9);/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */HAL_Delay(500);HAL_DMA_Start_IT(&hdma_tim1_ch1, (uint32_t)pixelclock, (uint32_t)&(GPIOA->BSRR), 4);HAL_TIM_OC_Start(&htim1, TIM_CHANNEL_1);}/* USER CODE END 3 */}void DMA_XFER_CPLT(DMA_HandleTypeDef *hdma){// Disable the DMA (not clear if this is necessary)HAL_DMA_Abort_IT(&hdma_tim1_ch1);// Stop the timerHAL_TIM_OC_Stop(&htim1, TIM_CHANNEL_1);// This appears necessary to deassert the trigger and prevent DMA triggering immediately when enabledTIM1->DIER &= ~(1 << 9);TIM1->DIER |= (1 << 9);}

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 25, 2022, 07:28:46 am
content: I'm not a fan of reviving old threads, but had to do this recently, can be done much simpler.CubeMX Timer config:- Memory (No addr. increase) ==> Peripheral (Addr. Increase)- Half-word (Both)- Circular modeCode:uint16_t pixelclock = Clock_Pin; // 16 bit, will be writing to lower/upper parts of BSRR (Set/Reset)HAL_DMA_Start(htim1.hdma, (uint32_t)&pixelclock, (uint32_t)&Clock_GPIO_Port->BSRR, 2);__HAL_TIM_ENABLE_DMA(&htim1, TIM_DMA_UPDATE);__HAL_TIM_ENABLE(&htim1);