7
\$\begingroup\$

I wish to transfer a large array of data to 8 output pins that are all on the same port. E.g. a 500 element 8bit array where each bit of each element represents the pin status. So 500 independent transfers to the port would be required.

I have been looking through the examples that ST have but none of them seem to do this. There seems to be a lot of 'DMA to PWM' examples which transfer to a pin that has a timer connected to it but this isn't useful to me since I want to transfer to a whole port. How would I do this? Are there any examples similar to this which I could modify to my need?

Thanks

\$\endgroup\$
  • 2
    \$\begingroup\$ Take a closer look at those DMA to PWM examples. You'll find that the DMA is dropping whole bytes into one of the Timer's registers and its the Timer that is doing the single-bit-banging on the one port pin. You just need to configure your DMA to send your whole bytes to the Port's output register. \$\endgroup\$ – brhans Jun 28 '16 at 16:26
  • \$\begingroup\$ I just tried editing the simple DMA example in STM32Cube_FW_F7_V1.4.0\Projects\STM32F746ZG-Nucleo\Examples\DMA\DMA_FLASHToRAM I changed the DMA direction register so its memorytoperipheral instead of memorytomemory. And I changed the destination memory address to one of the GPIO ports. For some reason its not working though and the GPIO pins aren't being written to at all. Here's the editted example code: ideone.com/h1EJBr \$\endgroup\$ – gorge Jun 30 '16 at 2:33
  • \$\begingroup\$ actually that was the wrong link i gave sorry ideone.com/KJTf1v \$\endgroup\$ – gorge Jun 30 '16 at 3:42
  • \$\begingroup\$ I got it working. It wasn't working was because i had DMA_MEMORY_TO_PERIPHERAL instead of DMA_MEMORY_TO_MEMORY Even though the former way is obviously the correct way. Could this be a bug? \$\endgroup\$ – gorge Jun 30 '16 at 13:40
  • \$\begingroup\$ I guess MEMORY_TO_PERIPHERAL means 'wait for an ack', in your case, the GPIO is being treated as memory-like (each write replaces the previous, no stalling from the peripheral). So I would dispute your 'obviously correct'. \$\endgroup\$ – Sean Houlihane Jul 2 '16 at 9:17
5
\$\begingroup\$

Short description:

Timer trig DMA with desired speed, DMA write data to GPIO output register.

Long description:

  1. Prepare selected GPIO port output state to default state low/high
  2. Configure selected GPIO port PX to output push-pull/open drain
  3. Configure any timer to desired period of GPIO port updating
  4. Configure selected DMA and link it to selected TIM
  5. Configure callbacks for DMA IRQs: HT, TC, ERR
  6. Enable DMA for write you data buffer, buffer length to selected PX->ODR
  7. Enable TIM to generate DMA events
  8. Run TIM
  9. Handle DMA state in callbacks and control TIM as you need

Code blank:

All code written right here and not tested, it may content simple mistakes such as using variable instead it pointer in HAL macros or functions calls.

GPIO_InitTypeDef GPIO_InitStruct;
TIM_HandleTypeDef htim1;
DMA_HandleTypeDef hdma_tim1_uev;

/* 1 (Port GPIOA) */
__HAL_RCC_GPIOA_CLK_ENABLE();
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_All, GPIO_PIN_RESET);

/* 2 (Port GPIOA) */
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(GPIOA, &GPIO_InitStruct);

/* 3 (Timer TIM1) */
__HAL_RCC_TIM1_CLK_ENABLE();
htim1.Instance = TIM1;
htim1.Init.Prescaler = DESIRED_PRESCALER;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = DESIRED_PERIOD;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
HAL_TIM_Base_Init(&htim1)

/* 4 (DMA1 Channel2) */
__HAL_RCC_DMA1_CLK_ENABLE();
hdma_tim1_uev.Instance = DMA1_Channel2;
hdma_tim1_uev.Init.Direction = DMA_MEMORY_TO_PERIF;
hdma_tim1_uev.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_tim1_uev.Init.MemInc = DMA_MINC_ENABLE;
hdma_tim1_uev.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; // 16 bits
hdma_tim1_uev.Init.MemDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_tim1_uev.Init.Mode = DMA_NORMAL;
hdma_tim1_uev.Init.Priority = DMA_PRIORITY_LOW;
HAL_DMA_Init(&hdma_tim1_uev);
__HAL_DMA1_REMAP(HAL_DMA1_CH2_TIM1_UP);
__HAL_LINKDMA(&htim1,hdma[TIM_DMA_ID_UPDATE],hdma_tim1_uev);
HAL_NVIC_SetPriority(DMA1_Ch2_3_DMA2_Ch1_2_IRQn, 0, 0); // enable DMA IRQ
HAL_NVIC_EnableIRQ(DMA1_Ch2_3_DMA2_Ch1_2_IRQn);

/* 5 (Callbacks for DMA IRQs) */
htim->hdma[TIM_DMA_ID_UPDATE]->XferCpltCallback = data_tramsmitted_handler;
htim->hdma[TIM_DMA_ID_UPDATE]->XferErrorCallback = transmit_error_handler;

/* 6 (Enable DMA) */
HAL_DMA_Start_IT(htim1.hdma[TIM_DMA_ID_UPDATE],(uint32_t)&my_data_buf, 
    (uint32_t)&GPIOA->ODR, my_data_buf_length);

/* 7 (Enable TIM for DMA events) */
__HAL_TIM_ENABLE_DMA(&htim1, TIM_DMA_UPDATE);

/* 8 (Run TIM) */
__HAL_TIM_ENABLE(&htim1);

/* 9 (DMA IRQ callbacks) */
void data_tramsmitted_handler(DMA_HandleTypeDef *hdma)
{
    /* Stop timer */
    __HAL_TIM_DISABLE(&htim1);
    /* Reconfigure DMA */
    HAL_DMA_Start_IT(htim1.hdma[TIM_DMA_ID_UPDATE],(uint32_t)&my_data_buf, 
        (uint32_t)&GPIOA, my_data_buf_length);
    /* Start timer for new data transmit */
    __HAL_TIM_ENABLE(&htim1);
}

void transmit_error_handler(DMA_HandleTypeDef *hdma)
{
    /* Stop timer */
    __HAL_TIM_DISABLE(&htim1);
    /* Some error handle ? */
}
\$\endgroup\$
  • \$\begingroup\$ Oh! 8bits data, if you use pins 0-7 just replace DMA_PDATAALIGN_HALFWORD in PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD and MemDataAlignment = DMA_PDATAALIGN_HALFWORD to DMA_PDATAALIGN_BYTE. \$\endgroup\$ – imbearr Jul 2 '16 at 8:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.