Skip to main content
LOliv.2
Associate
November 12, 2020
Solved

STM32F4 parallel input using HAL and DMA [SOLVED]

  • November 12, 2020
  • 13 replies
  • 6208 views

I have a very basic 8-bit parallel line on pins PC0-PC7 with its clock on PB6. The MCU is a 411RE, I'm using STM32CubeIDE and HAL libraries.

I've read AN4666 but the examples provided are written using an obsolete library, not HAL. But I understood that I have to use a Timer in Input Capture mode to activate the DMA transfer. To do this, in CubeMX I configured TIM4 as follows:

  • Slave Mode: Reset Mode
  • Trigger Source: TI1FP1
  • Internal Clock
  • Channel1: Input Capture direct mode

Then in the DMA Settings tab I added one line:

  • DMA Request: TIM4_CH1
  • Stream: DMA1 Stream0
  • Direction: Peripheral To Memory
  • Priority: Low
  • Mode: Normal
  • Increment Address: Memory
  • Use FIFO: yes
  • Word to Byte
  • Burst: Single

Does this configuration make any sense? My idea is to transfer the contents of register GPIOC->IDR (dimension: Word 32bit) to a uint8_t buffer (dimension Byte 8bit).

I also need an interrupt to count how many transfers were done or simply when the destination buffer is full. This is because I have to receive N 8bit frames before I can do anything with the data.

If the configuration is correct, I don't have any idea on how to use the HAL library to configure the source and destination registers and interrupts.

I have prepared a minimal setup with a button and the parallel lines connected to DIP switches.

Any help or hint is appreciated, it's a couple of days I am messing around trying to adapt stuff from examples written with the old library and from examples which use other DMA configurations, like moving data from ADC. But I the moment I did reach any kind of success.

Thanks

This topic has been closed for replies.
Best answer by waclawek.jan

"Peripheral" and "memory" are misleading terms in case of DMA, chosen for simplicity. P2M and M2P modes in fact mean, that transfers are started by requests from peripherals; M2M means that transfers are performed without any request, as soon as stream/channel is enabled, at the maximum possible pace - that's what you experienced, isn't it?

JW

13 replies

TDK
November 12, 2020

> Does this configuration make any sense? 

This is possible, but I don't think this is something supported by HAL. 90% sure.

> I also need an interrupt to count how many transfers were done or simply when the destination buffer is full.

The NDTR register counts the number of transfers remaining. You can trigger an interrupt when this reaches zero.

> Any help or hint is appreciated

This code will move data from memory to GPIO. It should be relatively straightforward to modify it operate in the opposite direction:

// set up the timer
 __HAL_RCC_TIM1_CLK_ENABLE();
 __HAL_RCC_TIM1_FORCE_RESET();
 __HAL_RCC_TIM1_RELEASE_RESET();
 TIM1->PSC = 0;
 TIM1->ARR = 400;
 TIM1->CNT = 0;
 TIM1->DIER |= TIM_DIER_UDE;
 
 uint32_t gpio_data[] = {
 (1 << 10) | (1 << 11),
 1 << 26,
 (1 << 10) | (1 << 27),
 1 << 26};
 
 // set up the DMA channel
 __HAL_RCC_DMA2_CLK_ENABLE();
 __HAL_RCC_DMA2_FORCE_RESET();
 __HAL_RCC_DMA2_RELEASE_RESET();
 
 DMA_Stream_TypeDef * stream = DMA2_Stream5;
 stream->M0AR = (uint32_t) gpio_data;
 stream->NDTR = sizeof(gpio_data) / (sizeof(*gpio_data));
 
 // disable direct mode
 stream->FCR |= DMA_SxFCR_DMDIS;
 //stream->FCR |= 0b11 << DMA_SxFCR_FTH_Pos;
 
 // TIM1_UP is channel 6 on DMA2 stream 5
 stream->CR |= 6 << DMA_SxCR_CHSEL_Pos;
 stream->CR |= 0b10 << DMA_SxCR_MSIZE_Pos;
 stream->CR |= 0b10 << DMA_SxCR_PSIZE_Pos;
 stream->CR |= DMA_SxCR_MINC;
 stream->CR |= DMA_SxCR_CIRC;
 stream->CR |= 0b01 << DMA_SxCR_DIR_Pos;
 stream->PAR = (uint32_t) &GPIOC->BSRR;
 stream->CR |= 0b1 << DMA_SxCR_PL_Pos;
 
 stream->CR |= DMA_SxCR_EN;
 
 // start timer
 TIM1->CR1 |= TIM_CR1_CEN;

"If you feel a post has answered your question, please click ""Accept as Solution""."
Piranha
Principal III
November 15, 2020

By the way, for lines 30-39 a temporary variable for CR modification would generate much less instructions.

waclawek.jan
Super User
November 17, 2020

Or just a single write ORing the values together.

stream->CR = 0

| (6 << DMA_SxCR_CHSEL_Pos)

| (0b10 << DMA_SxCR_MSIZE_Pos)

etc.

Any reasonably optimizing compiler would throw the temporary variable away, anyway; but I consider it unnecessary noise.

But this is just style and a relatively minor inefficiency; the point remains the same.

JW

waclawek.jan
Super User
November 19, 2020

For timer-triggered transfers, don't use M2M but P2M.

JW

LOliv.2
LOliv.2Author
Associate
November 19, 2020

But I don’t need mem-to-mem for transferring from GPIOC->IDR to a buffer in memory?

TDK
November 19, 2020

GPIO is a peripheral.

"If you feel a post has answered your question, please click ""Accept as Solution""."
waclawek.jan
waclawek.janBest answer
Super User
November 21, 2020

"Peripheral" and "memory" are misleading terms in case of DMA, chosen for simplicity. P2M and M2P modes in fact mean, that transfers are started by requests from peripherals; M2M means that transfers are performed without any request, as soon as stream/channel is enabled, at the maximum possible pace - that's what you experienced, isn't it?

JW

LOliv.2
LOliv.2Author
Associate
November 22, 2020

>  that's what you experienced, isn't it?

Yes absolutely, I read again the reference manual with this concept in mind and everything makes more sense. Especially I missed the first sentence of section 9.3.6, which says that source and destination can be any address in the 32bit.

Anyway thanks a lot for your help. I managed to obtain what I needed. Also using CubeMX and HAL, which was preferred for the project. I will share in this thread my solution when I will have some time.

One error that took me quite a while to catch is that I was writing as source address just (uint32_t)GPIOC->IDR instead of (uint32_t)&GPIOC->IDR. Since just a plain uint32_t is expected, instead of a pointer, the compiler didn't complain. Not a great design choice of the HAL library IMHO.

Still, I have one last problem: my configuration seems to work only with TIM1, which on the F411RE can trigger DMA2 Str1 Ch6.

If I regenerate the project with for example TIM4 triggering DMA1 Str0 Ch2, or TIM5 with DMA1 Str2 Ch6, the DMA transfer raises a transfer error interrupt.

My code is left untouched between the CubeMX regenerations, I just change the references to the timer handle, otherwise it wouldn't even compile. The Input Capture interrupt works fine.

I can't understand if I'm doing something wrong when using the other timers, or again I am misinterpreting the documentation and it can't be done. Thanks again for your time.

waclawek.jan
Super User
November 22, 2020

Look at Fig.1 in the RM.

Even if the two DMAs are almost identical in their working and have identical registers, their connection within the system is not the same: the Peripheral port of DMA1 has no connection into the busmatrix, but is connected directly to the APB1 bus.

Even the Memory port of DMA is not connected to all resources on the busmatrix, only to the memories.

So to access GPIO, you have to use DMA2, thus TIM1 (or TIM8 if your variant of 'F4 contains it).

JW

LOliv.2
LOliv.2Author
Associate
November 23, 2020

Nice, makes sense thanks. Would never occurred me to take a look at first figure if the manual. Now I guess that Figure 23 could also be valid to understand this fact.

I will now share my configuration for a sample demo that captures 8-bit data from GPIOC pins 0-7 when a button connected to TIM1 channel 1 in Input Capture mode (also called Compare in TIM1) is pressed. The program sends through UART the data nicely formatted. When the serial transfer is complete a UART TransferComplete interrupt enables again DMA for a new transfer.

A couple of quirks: if the timer is used just as a mean to trigger DMA or a IC interrupt, it doesn't need to have the clock enabled. Another maybe strange thing is that DMA is configured to move bytes, while the GPIOC IDR register is 32 bit, although 16bits are reserved. The motivation of this choice is that in my application I can use the first 8 contiguous pins as in input. This allows me to use a receiving buffer of bytes, saving memory.

/* USER CODE BEGIN PD */
#define LEN 10
#define TXLEN (LEN * 9) + 2
/* USER CODE END PD */
 
 
/* USER CODE BEGIN PV */
uint8_t buff[LEN];
uint8_t txBuff[TXLEN] = { };
uint8_t transferComplete = 0;
/* USER CODE END PV */
 
 
/* USER CODE BEGIN PFP */
static void TransferComplete(DMA_HandleTypeDef *DmaHandle);
static void TransferError(DMA_HandleTypeDef *DmaHandle);
static void HalfTransferComplete(DMA_HandleTypeDef *DmaHandle);
/* USER CODE END PFP */
 
 
/* USER CODE BEGIN 2 */
 
	// Attach DMA callback functions
	htim1.hdma[TIM_DMA_ID_CC1]->XferHalfCpltCallback = HalfTransferComplete;
	htim1.hdma[TIM_DMA_ID_CC1]->XferCpltCallback = TransferComplete;
	htim1.hdma[TIM_DMA_ID_CC1]->XferErrorCallback = TransferError;
 
	// Start DMA in interrupt mode, specify source and destination
	HAL_DMA_Start_IT(htim1.hdma[TIM_DMA_ID_CC1], (uint32_t) &GPIOC->IDR, (uint32_t) buff, LEN);
 
	// Enable timer to trigger DMA transfer - CC1DE bit
	__HAL_TIM_ENABLE_DMA(&htim1, TIM_DMA_CC1);
 
	// Enable timer input capture
	HAL_TIM_IC_Start(&htim1, TIM_CHANNEL_1);
 
 /* USER CODE END 2 */
 
 /* Infinite loop */
 /* USER CODE BEGIN WHILE */
	while (1) {
		if (transferComplete) {
			transferComplete = 0;
 
			// Transmit buffer to UART
			// Prepare data into buffer
			txBuff[0] = '#';
			txBuff[1] = '\n';
			for (int i = 0; i < LEN; i++) {
				for (int k = 0; k < 8; k++) {
					uint32_t val = buff[i] & (1u << k);
					txBuff[(i * 9) + k + 2] = val == 0 ? '0' : '1';
				}
				txBuff[(i * 9) + 10] = '\n';
			}
 
			HAL_UART_Transmit_IT(&huart2, txBuff, TXLEN);
		}
 
 /* USER CODE END WHILE */
 
 /* USER CODE BEGIN 3 */
	}
 /* USER CODE END 3 */
 
// ################################################
// ################################################
 
/* USER CODE BEGIN 4 */
 
static void HalfTransferComplete(DMA_HandleTypeDef *DmaHandle) {
	HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
}
 
static void TransferComplete(DMA_HandleTypeDef *DmaHandle) {
	HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
 
	transferComplete = 1;
}
 
static void TransferError(DMA_HandleTypeDef *DmaHandle) {
	HAL_GPIO_TogglePin(LED3_GPIO_Port, LED3_Pin);
}
 
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
	// Enable again DMA for a new transfer
	HAL_DMA_Start_IT(htim1.hdma[TIM_DMA_ID_CC1], (uint32_t) &GPIOC->IDR, (uint32_t) buff, LEN);
}
 
/* USER CODE END 4 */

0693W000005CFhxQAG.png0693W000005CFhsQAG.png

Karim
Associate III
November 19, 2021

Hi LOliv.2, Thank you for sharing your configuration. I searched a lot and tried for couple of days too about same question (Parallel Input using DMA and Timer). I have some questions and I appreciate if you help:

  1. You shared a code and I think it's piece of main.c. Did you add these codes to main.c which is created by cubemx? Because I don't see Timer Init DAM Init etc.
  2. Did you share your project somewhere i.e. in GitHub? And if Not can I ask to share it?

Thank you.