cancel
Showing results for 
Search instead for 
Did you mean: 

Automatic DMA 32bit -> 4 byte UART transfer?

DavidAlfa
Senior II
 
7 REPLIES 7
Uwe Bonnes
Principal III

Have a look at the refernce manual for the chip you use. There are such mechanisms available, look if they fit for you.

DavidAlfa
Senior II

Testing this on STM32F411CE.

I'm using Timer 2 to:

- Trigger ADC using TRGO, start conversion and DMA2 transfer, writing to "ADC_value" variable.

DMA config:

  • circular
  • SRC=half-word, ADC1->DR
  • DST=half-word, my_value
  • incr=0 for both
  • Dir: peripheral->memory

Works correctly.

- Use T2_UPDATE source to trigger DMA1 write from "ADC_value" to USART2->DR.

DMA config:

  • circular
  • SRC=half-word, my_value
  • DST=byte, USART2->DR
  • incr=0 for both
  • memory->peripheral

Partially works, this is where I'm struggling.

I'm trying to force the DMA + FIFO to split a single 16 or 32-bit read into multiple UART TX transfers.

So for a single DMA requests, the DMA loads the data into the fifo as 4-byte, and then the fifo starts unloading onto the UART as single byte transfers.

I expected the fifo to keep writing to the UART until empty, but I'm getting a single byte transfer on every DMA request.

My current code is:

volatile uint32_t my_value;
void main(void){
    // HAL_Init, etc
 
    // Prepare ADC (Set in TRGO mode, using DMA2 transfer and continuous dma requests)
    HAL_ADC_Start_DMA(&hadc1, &my_value, 1);
 
    // Prepare Timer2, clear counter and flags
    __HAL_TIM_SET_COUNTER(&htim2,0);
    __HAL_TIM_CLEAR_FLAG(&htim2,TIM_FLAG_UPDATE);
 
    // Prepare DMA1
    HAL_DMA_Start_IT(&hdma_tim2_ch3_up, (uint32_t)&my_value, (uint32_t)&USART2->DR, 1);
 
    // Start Timer2 (Enable interrupt for toggling LED, for debugging purposes)
    HAL_TIM_Base_Start_IT(&htim2);
 
    // Enable Timer2 update DMA trigger for DMA1 transfer
    __HAL_TIM_ENABLE_DMA(&htim2, TIM_DMA_UPDATE);
 
    while(1);
}

I also tried setting DMA1 in byte mode for both and setting a 2 or 4-byte transfer size, without sucess. Maybe USART->DR has to be accessed as 16 or 32-bit?

Another issue I'm thinking of is that dma will probably write the data very fast, without any control, as it's not being handled by UART_TX request.

> Maybe USART->DR has to be accessed as 16 or 32-bit?

No, USART does not support data packing as SPI in some newer STM32 does.

> I expected the fifo to keep writing to the UART until empty,

What makes you to expect this, except your wishes? I mean, where did you see this behaviour to be described?

> but I'm getting a single byte transfer on every DMA request.

This is what DMA does. Upon one DMA request, it performs one DMA transfer. DMA transfers are counted on the Peripheral side. Memory side fills/empties FIFO according to FIFO settings (if FIFO is enabled).

One way to do what you want would be to generate a relatively long TRGO from the timer you use to trigger ADC and feed that as TRGI to another timer, which Slave-mode controller would be set to Gated. Set that timer to run fast so that it generates exactly two/four Updates during the TRGO duration and use those to trigger the DMA (OTOH that timer must run slow enough for UART to succeed to transmit the data, of course).

I personally wouldn't use Cube/HAL for this, as with anything outside the simplistic tasks which could be clicked in CubeMX, it just gets into way.

JW

DavidAlfa
Senior II

Thanks for the clarification :)

Yep, that's what I thought, but the DMA packing / fifo / endianess section is a little fuzzi, so I had to try!

Why did I thought that?

Well, you can trigger a memory-to-memory transfer of any size (well, up to 64K) and it will do the whole thing itself, so I thought it could be also possible to do the same with a peripheral somehow.

Using a secondary timer would work but, but sadly not optimal, I was trying to make a silent, unassisted bridge between adc and uart so I could debug everything without any CPU intervention, even at few Ksps, with zero interrupts wasting power.

> silent, unassisted bridge

That's exactly what you'd achieve using the second timer to trigger transfers.

JW

DavidAlfa
Senior II

Yep, that's what I'm trying to do!

It's easy to run a second timer 4 times faster to send 4 bytes every one primary Timer clock cycle, however the clock division must be perfect or it will cause drift.

This is an issue in higher frequencies, for example if your primary timer period is 3125, you can't run it exactly at 1/4 the period.

HAL wasn't really an issue here, requires some reading at the libs but you'll eventually get there:

// Start ADC (Configured with Timer 2 TRGO)
  HAL_ADC_Start_DMA(&hadc1, &adc_val, 1);
  
  // Clear flags and counters
  __HAL_TIM_SET_COUNTER(&htim2,0);
  __HAL_TIM_SET_COUNTER(&htim3,0);
  __HAL_TIM_CLEAR_FLAG(&htim2,TIM_FLAG_UPDATE);
  __HAL_TIM_CLEAR_FLAG(&htim3,TIM_FLAG_UPDATE);
  
  // Start mem->USART TX  DMA, send 2 bytes in circular buffer
  HAL_DMA_Start(&hdma_tim3_ch4_up, (uint32_t)&adc_val, (uint32_t)&USART2->DR, 2);
 
   // Enable Timer 3 DMA signal
  __HAL_TIM_ENABLE_DMA(&htim3, TIM_DMA_UPDATE);
  
  // Test value
  adc_val = 0xAA55;
  
  // Start Timer 2 (Triggers ADC conversion)
  __HAL_TIM_ENABLE(&htim2);
 
  // Cheap way to detect first conversion
  while (adc_val==0xAA55);
 
  // Start Timer1 (UART DMA starts receiving transfer requests)
  __HAL_TIM_ENABLE(&htim3);

DavidAlfa
Senior II

Added a simple project for everyone