cancel
Showing results for 
Search instead for 
Did you mean: 

How to send data from ADC diretly to FMAC using DMA (peripheral-to-peripheral)?

Helder Sales
Associate II

Hi,

I'm trying to send data from ADC to FMAC directly via DMA in circular mode. From the STM32G4 reference manual (dm00355726) page 402:

"Peripheral-to-memory, memory-to-peripheral, memory-to-memory and peripheral-to-peripheral data transfers"

But I can't find anything on STM32CubeG4 that configures that. Here's a screenshot:

0693W000006EshuQAC.png 

I've tried writing to FMAC write data register directly using

HAL_ADC_Start_DMA(&hadc1, &FMAC->WDATA, 1)

But it doesn't work.

What worked for me was setting the DMA circular mode for both ADC and FMAC, then syncronize it using DMAMUX requests. Although this eliminates the CPU in the process, it doesn't feel like a true peripheral-to-peripheral transaction.

How can I make a DMA transfer content from one peripheral to another, directly?

Kind regards,

Helder

1 ACCEPTED SOLUTION

Accepted Solutions
Igor Cesko
ST Employee

You are right - those register can be accesses only by 32-bit. This is mistake in RM0440 (for WDATA and RDATA registers). All FMAC registers should have only word access. We will modify description of WDATA and RDATA FMAC register: "Access: word access".

I have tested the behavior (DMA and direct CPU write to WDATA register) and tests confirmed that 16-bit access is not working (only 32-bit access is correct).

Regards

Igor

View solution in original post

19 REPLIES 19

Cube is not suitable for anything "unusual".

> it doesn't work

This is not a helpful information.

Read out and post the content of DMA/DMAMUX and if needed, other peripheral registers content.

JW

Maybe you just need Cube to spit out some placeholder code, and configure it suitably manually, based on a reading of the reference manual, and available plumbing of ADC vs FMAC on the bus matrices.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

My apologies. I've read the DMA contents.

DMA1 registers was as follows:

ISR 0x7

CCR1 0x35AF

CCR4 0x36AF

CNDTR1 0x1

CPAR1 0x50000040

CPAR4 0x4002141C

CMAR1 0x3CD

CMAR4 0x200002A0

I think based on CMAR1 that I've provided the wrong address (0x3CD, should be 0x40021418 from what I've seen in the debugger), so I did that:

void *address = 0x40021418;
HAL_ADC_Start_DMA(&hadc1, address, 1);

And now the DMA1 content is

ISR 0x7

CCR1 0x35A0

CCR4 0x36AF

CNDTR1 0x1

CPAR1 0x50000040

CPAR4 0x4002141C

CMAR1 0x40021418

CMAR4 0x200002A4

I don't want to use DMAMUX, if that's possible. But, just in case, the readings are:

C0CR 0x5

C3CR 0x6E

Too bad peripheral-to-peripheral is "unusual". It should be usual IMO, because this could save resources and time.

Edit: I'm not sure if I'm violating any rules trying to write directly on the FMAC Write data register, because it seems from the Ref. Manual that is r/w.

They should include the configuration in the Cube, or maybe they will in a far future. Too bad it isn't implemented now, because this feature (periph2periph) is very interesting. These things tends to be a pain to deal manually, at least in my experience.

Okay, and what are the symptoms, now?

You maybe should set NDTR to a higher value than 1, to see it running. Of course, you then also want MINC=0.

I don't use the 'G4 so don't know much about FMAC. As you are pushing data at its input side, it should not attempt to pull through its DMA. I don't know how much the internal state of FMAC is exposed, you maybe want to start with something more straightforward to observe like UART.

> I don't want to use DMAMUX, if that's possible.

No, but for the basic operation it's simple, as you see, it's just about setting the proper source number. Good you got right the numbers; that DMAMUX is numbered from 0 and DMA from 1 is an idi^H^H^Hpoor design choice on behalf of ST.

> These things tends to be a pain to deal manually, at least in my experience.

So, you prefer to click quickly, and then perform a lengthy search for the reason why it failed.

JW

> Okay, and what are the symptoms, now?

It triggers the ADC_DMAError() (transfer error) callback.

> you maybe want to start with something more straightforward to observe like UART

You're right, I should have started with "simpler" peripherals. It sure is better than trying with FMAC, but unfortunately it seems to lack documentation on how to perform the so called DMA peripheral-to-peripheral as well. I wish ST provided better examples and documentation, these "new" things tends to be kinda nebulous...

The ref. manual (dm00355726), page 409 says:

Peripheral-to-peripheral mode

Any DMA channel can operate in peripheral-to-peripheral mode:

• when the hardware request from a peripheral is selected to trigger the DMA channel

This peripheral is the DMA initiator and paces the data transfer from/to this peripheral

to/from a register belonging to another memory-mapped peripheral (this one being not

configured in DMA mode).

• when no peripheral request is selected and connected to the DMA channel

The software configures a register-to-register transfer by setting the MEM2MEM bit of

the DMA_CCRx register.

From what I understood, the first way is what I'm trying to achieve, using one peripheral DMA to tranfer its contents to a register on the other peripheral. The second way I'm not sure what it wants me to do.

> So, you prefer to click quickly, and then perform a lengthy search for the reason why it failed.

Actually, I prefer to click quickly AND have things working. I remember from the TM4C series from TI that I had to do DMA manually, align the µDMA memory, and throw some lenghty code and test over and over again to learn how it works.

The entire microcontroller was more "manual", having to use TivaWare and reading the user manuals, doing the peripheral initializations by myself and all that.

The STM32Cube, on other hand, seems to take away the burden of configuring peripherals and let the user do what they want right away. Not that I didn't like to program the TM4C devices, I actually enjoyed myself when I was still using them.

So why would you say that? It is bad to use STMCube whenever possible? Or I'm missing something?

Oh, my.

The basic problem is, that ST does not know how to write concise documentation and decent simple examples.

The other problem is, that they attempt to replace the missing documentation and decent simple examples by the clicky (Cube). There's only a miniscule subset of all possible setups which can possibly be ever captured by the clicky, and with expanding hardware/families things just get worse.

The third problem is, that ST refuses to accept that the first two things are a deep problem which ultimately will lead to their end.

Back to the basic problem: there is no such thing as "peripheral to peripheral DMA mode". (ST apparently tried to reformulate the DMA chapter, as users were confused on the past, but they made things worse.) There are two kinds of DMA in the STM32, a single-port and a double-port one. In 'G4, it's the single-port, the same or similar as in 'F1, 'F0, 'F3 and the 'Lx. It is described in AN2548 (although in a rather poor way- ST appnotes are lacking and often just reiterate the material in RM with little added - still I recommend you to read it). The difference is in how the requests (triggers) are routed, in older families they are ORed or demultiplexed into fixed channels, here the DMAMUX provides flexible routing.

So, the DMA is not much more than two pointer registers and some glue logic. When the request arrives, DMA reads from the source side and writes to destination side, and decrements NDTR. Which port is source and which is destination, is given by CCR.DIR. While the pointers are named "peripheral" and "memory", that has nothing to do with peripherals and memories, as such. The DMA is slightly asymmetric in timing so that's the reason to call the ports "peripheral" and "memory", but that's not that vital to this discussion. (As a bonus, there's a special setting, called mem2mem, where requests are ignored and data are simply churned from source to destination at the maximum rate).

So that maybe explains a bit the mess in the manual (and we here prefer the RM0440 - style notation to the ST-internal DMxxxxxxx) and maybe it makes you more confident in dealing with the DMA without having to lean on Cube/HAL.

As I've said, I'd start with UART as destination (easily observable in a terminal program or using LA), I'd set high NDTR and observe it changing in debugger (the observable address registers for some inexplicable reason don't change, the real address registers are hidden to the user), and of course nonincrementing on both "peripheral" and "memory" side.

ADC is quite a beast and it tends to vary quite significantly between the families. As I've said, I don't use the G4; but generally, if you skip/skim the parts which are obviously "advanced", the basic working is relatively easy to grab from reading the ADC chapter. Concentrate on the narrative related to DMA.

ADC needs data to be picked whenever available, and if not, it may stick (see ADC_CFGR.OVRMOD). There are status bits to be investigated in that case, indicating where the problem may be. There's also some gotcha with circular DMA in some families, see ADC_CFGR.DMACFG.

JW

> The basic problem is, that ST does not know how to write concise documentation and decent simple examples.

> The other problem is, that they attempt to replace the missing documentation and decent simple examples by the clicky (Cube). There's only a miniscule subset of all possible setups which can possibly be ever captured by the clicky, and with expanding hardware/families things just get worse.

This. I just wanted to work with the FMAC in my graduation project, but at the time, there was no time to deal with the lack of documentation, so I used the CMSIS DSP library.

What I simply wanted was to read some distorted sinusoidal wave and filter it using the FMAC, but there's no example of how to use the FMAC with ADC. To make things worse, the examples are overcomplicated: It separates inputs in four parts and outputs in two parts, no circular DMA, and s**tloads of flags...

Not only that, but when I run FMAC in DMA mode (except for FMAC_PRELOAD DMA that I don't use), there aren't functions ending with _DMA, for example, as in HAL_ADC_Start_DMA(). The HAL_FMAC_FilterStart() function stays the same, which is quite confusing (isn't _DMA supposed to be a convention when using DMA mode?).

Aaand, is quite amusing that I have to do this:

int16_t inputSize = 1;
 
if(HAL_FMAC_AppendFilterData(&hfmac, &g_adcData, &inputSize);

The same applies to output. I have to create a local variable just to pass 1 as argument (or any number if I need). Really?

Well, I think I should be thankful enough that I make the FMAC working with circular DMA on both input and output, synchronized with the ADC and no CPU involved. The flow of information is as follows:

ADC --- MEM --- FMAC --- MEM

That's good enough, even if it isn't peripheral-to-peripheral directly. I'll try again later once I figure out how to make the DMA working with registers directly, just for the sake of learning (and hoping to use in other peripherals in the future).

> So that maybe explains a bit the mess in the manual (and we here prefer the RM0440 - style notation to the ST-internal DMxxxxxxx) and maybe it makes you more confident in dealing with the DMA without having to lean on Cube/HAL.

That was very, very insightful and helpful thank you! So that means even the other series 'F' and 'L' could benefit from this.

I guess I'll just stick for now with the UART as it is better for debugging. And I'll make more frequent use of registers when I can.

Please let us know if your findings.

JW