Spark Logic

Problems with regular simultaneous dual ADC conversion with DMA transfer

robca Tue Nov 24, 2015 5:06 pm

EDIT: I finally solved this, please see http://www.stm32duino.com/viewtopic.php … 8474#p8474 for more info

I’m at the end of my wits :? I have read the ADC and DMA docs at least 5 times each. Looked at the RegSimul_DualMode example from ST (and that was fun enough, since it uses completely different conventions than the Arduino/Maple libraries but in the end I understand what it does)… and I still can’t make it work… help, please.

Problem statement: on a Maple Mini, using STM32duino, convert 4 analog channels (connected to pins D3-D6, i.e. IN8-5, respectively) and transfer the data in memory using DMA (I need fast and predictable timing).

I basically would need to combine “dual simultaneous mode” with “continuous scan”, in order to capture ~2000 samples from each channel. But I fear it might not be possible, and the only mention I found is here (no reply, though) https://my.st.com/public/STe2ecommuniti … x?tags=adc

The code below sets a sequence for each ADC: in SQR1 sequence length is 1 (i.e. 2 conversions), and I set the SQR2 registers to have ADC1 convert IN8 while ADC2 converts IN7, then ADC1 convert IN6 while ADC2 converts IN5. I set regular (not injected) SW triggers.

After setting the ADC1 and ADC2 registers and the DMA1 Channel 1, in theory setting ADC1->regs->CR2 |= ADC_CR2_SWSTART; should start the conversion for the master ADC1 and slave ADC2 and in parallel start the DMA (one 32 bit DMA transfer for each conversion: in dual mode the ADC1 SR register holds both the values for ADC1 and ADC2).

What I noticed, though, is that when ADC_CR2_CONT is enabled, the ADC continuously scans, and the DMA keeps sending data to my buffer. What that means is the the order of the odd and even channels in the buffer can be off. Basically, when ADC_CR2_CONT is enabled, the ADC keeps scanning all the time, and that triggers DMA transfers in a random order.

If I don’t use ADC_CR2_CONT, I can only sequence a limited number of channels (and even if I repeat the sequence, I can only still do 16 conversions per ADC max (using SQR1-3). Or I can convert 2 channels (one on ADC1, one on ADC2) continuously in parallel…

But there seems no way to do scan and dual simultaneous at the same time… am I missing something? Any suggestion? If this doesn’t work, I can use two Maple Mini synchronized to capture 4 channels, but it makes everything harder to code (one as a master, one as a slave, with the conversion triggered at the same time, and transferring data)

Here’s my non-working code (if you uncomment the delay() statement you will see the problem)

EDIT: removed non-working code, see http://www.stm32duino.com/viewtopic.php?f=3&t=757&p=8474#p8474 for working code

Replies

RogerClark Tue Nov 24, 2015 9:43 pm

Sounds like a question to post to STMs own forum.
However I would not mention you are using the Arduino IDE etc, as I get the impression they only like people using their CMSIS / HAL

But you could post the question with the same technical detail as above and see if they can tell you if the hardware is capable of doing this.

The other option is to use external switching or external ADCs, or both, i.e use one very fast external ADC and an analog multiplexer or mulichannel input

e. mcp3208

( IF and its a big IF something like to mcp3208 can be preprogrammed to cycle through 4 inputs and just have data pulled via SPI DMA )

mrburnette Tue Nov 24, 2015 10:31 pm

RogerClark wrote:
<…>
( IF and its a big IF something like to mcp3208 can be preprogrammed to cycle through 4 inputs and just have data pulled via SPI DMA )

RogerClark Tue Nov 24, 2015 10:39 pm

Ray.

Good point.

The PSOC is almost certainly a better device for this application

(PS. Still not got around to using mine even though as I have 3 of them I could unbrick them if I stuffed up by not including the bootloader module ;-)

robca Tue Nov 24, 2015 10:59 pm

I will post it to a few more forums first, but the ST one seems to have a pretty poor track record of replying (and thanks for the heads up on using their code, I actually looked at what they have, so it would be easy to create an equivalent

I also just saw that the F303 actually has 4 ADCs (and they can be programmed to be 12/10/8/6 bits). I’m still getting up to speed on the STM32 family, does anyone have a good F303 board to suggest? Would this be a good starting point? http://www.newark.com/stmicroelectronic … scovery+F3

I’d love if something like this actually were manufactured: http://sustburbia.blogspot.co.uk/2014/0 … 3-arm.html. I know I could build my own using this http://ebrombaugh.studionebula.com/embe … index.html, but building such a small pitch SMD board is not that simple

Any other “cheap” F303 boards that work with STM32duino? (I’m assuming that the eval kit above does work)

Thanks for the PSOC suggestion, I’ll look into it. My only concern is that’s yet another platform to learn, while with the F303 I can keep leveraging the same set of skills, and there are a lot of STM32 boards getting cheaper and cheaper and easier to find. I’m assuming that the PSOC won’t work anytime soon with STM32duino…

RogerClark Tue Nov 24, 2015 11:10 pm

Our support on the F3 is very weak.

All I did was adopt someone else’s F3 version of LibMaple, however I’ve not had time to try it at all, and its not been maintained or updated like the F103 stuff has been

If the F4 series has the same capabilities as the F303 then I’d recommend you look at the F4 instead.
We have reasonable support for the F4 (but not as good as the F103)

You can get reasonably priced F407 boards like this one http://www.aliexpress.com/item/5pcs-lot … eb201560_7

And I think you can get even smaller and cheaper ones (I’m sure someone posted a link a while ago, but I can’t remember what it was with reference to)

BTW. Those little F303 boards look nice, but I’ve found that soldering the MCU’s is difficult even on the F103 (I’ve ruined several boards trying)

robca Tue Nov 24, 2015 11:51 pm

Thanks for your willingness to help.

Unfortunately I can’t find an F4 chip that has 4 ADCs… 3 is as high as I can find (which is not to say it doesn’t exist, just that I can’t find it :))

EDIT: I just discovered that the F3 family is designed with signal processing in mind, that’s why it has much faster ADC, more channels, more configurability, better DAC, etc… F4 tends to be rather lacking in the ADC compartment

Given what you say about F3 and F4 support, i think I’ll either look into the PSOC or go to my backup plan (use 2 F103 as master slave).I might still get an F303 just to play with it, next time I need something from Mouser or Newark, $10.88 aren’t much money (the main downside of that board is that a lot of pins seem to be connected to on board peripheral, like accelerometers, etc)

mrburnette Tue Nov 24, 2015 11:52 pm

robca wrote:
<…>
My only concern is that’s yet another platform to learn, while with the F303 I can keep leveraging the same set of skills, and there are a lot of STM32 boards getting cheaper and cheaper and easier to find. I’m assuming that the PSOC won’t work anytime soon with STM32duino…

robca Wed Nov 25, 2015 12:11 am

mrburnette wrote:

PSoC is GNU and the free compiler default usage is bundled with a GNU distribution. You can also purchase Keil, a limitrd eval version is downloadable for free.

PSoC is unlike any uC you have used, unless you are into Verilog. The default drag-n-drop designer software has a VHDL back-end to configure switched network fabric snd pins I/O… just about any package pin can be assigned independently. The uC is ARM M0+ running at 48MHz.

The PSoC will never be an Arduino board under the ArduinoIDE. The PS0C 4200 prototype board is $4. The $20’ish 4200 Pionerr board has builtin hardware debug to the C-source code, with variables view, breakpoints, etc.

Ah, I am old and retired, I live to learn YAP (yet another platform.)

Ray

RogerClark Wed Nov 25, 2015 12:21 am

Ray is the expert…

But the Cypress PSoC 4200 is 48Mhz instead of the 72Mhz on the STM32

Actually if you want to stay with something like the STM32 but its not quite fast enough, the GD32 boards are faster and the same price.
GD32 are specced to run at 108Mhz and have zero wait state flash memory which makes them faster even at 72Mhz

96Mhz is the fastest you can run the GD32 and still have USB and be within their spec, however if its just for a hobby type thing, then I’m running my GD32’s at 120Mhz , which is another multiple that supports USB, and it works fine under normal room termperature operation.
I actually measured the temperature with an IR thermometer and the chips do get a few deg hotter when tunning at 120Mhz compared with 72Mhz but really nothing that would concern me.
And if it really was an issue, you could always stick on one of those little heat sinks that they use for the stepper driver chips on the reprap 3d printer RAMPS board.

robca Wed Nov 25, 2015 12:32 am

My concern (quickly scanning the PSOC documentation) is that PSoC 4200 seems to have a single ADC, serially scanning channels. That I can do as easily (and as fast, for my purposes) with a F103. Since the F103 uses well defined conversion times and DMA, each sample is subtly time-shifted, but the waveform can be interpolated and cross correlated as easily on the F103 as the PSoC. If so, using the PSoC doesn’t seem to add much value, especially considering that I can get an F103 with 64K RAM, hence sample faster (and have better interpolation thanks to oversampling)

With the F103, I might also find a way to use the parallel mode and some fast code to fill a buffer (need to mull this over for a bit)… With a F303, I can do 4 conversions at once, not requiring interpolation.

RogerClark Wed Nov 25, 2015 12:41 am

With the STM32 you can do parallel operations with multiple DMA channels running and also your code running at the same time with practically no loss in performance.

BTW.
I know you are not using SPI, but FYI
With the current SPI DMA code, it’s “blocking” and does not return until the transfer is complete, however we could add some new variants of the dmaTransfer() function, one of which could have a completion callback.

Even our current “blocking” DMA SPI is much faster than getting the ARM processor to push data to the SPI subsystem

ahull Wed Nov 25, 2015 1:36 am

Do we have some scale on the numbers involved.

We have established that the signal is in the audio domain, so <20kHz, the STM32F103 ADC is capable of 2Mhz fast interleaved mode, as demonstrated on the Pig-O-Scope (so about 100 times faster than would be needed for audio).

What sample rate do we require to give us our time delay?

What is the distance between the mics, and what resolution do we require from our signal (i.e. how accurate does our result need to be).

Which STM32 ADC conversion mode is most appropriate for our application, I would suggest Dual regular simultaneous mode, but I’m open to other suggestions.

zmemw16 Wed Nov 25, 2015 2:26 am

as you’re asking about numbers…

does it have to be dual? simultaneous?

how fast could it do sequential conversions of the 4 channels, adc1, adc2, adc3, adc4 ? (and dma them?)

what would the resulting phasing difference in the readings cause in terms of the calculations (distances)?

what are you expecting distance accuracy to be?

what’s 4m/5m at speed of sound? mS uS nS

really big wall:-)
stephen

ahull Wed Nov 25, 2015 10:24 am

zmemw16 wrote:as you’re asking about numbers…

does it have to be dual? simultaneous?

robca Wed Nov 25, 2015 2:58 pm

First of all, let me share good news. I finally figured this out, thanks for your posts. the STM32F103 can actually perform “regular simultaneous dual conversion with DMA transfer”. It took quite a lot of time to figure out. Read on if you like a mystery novel… and like any mystery novel, once explained it all makes sense. I’m taking the time to explain it fully, because as far as I know there’s no explanation of any of this in either the ST docs nor online (at least, not breaking down every single step)

It turns out that using both continuous conversion and DMA creates a few tricky timing conditions that have to be met. It doesn’t matter when used like in the O-Scope, since you are scanning only one channel per ADC. When scanning more than one channel per ADC, timing matters a lot.

The moment you enable the ADC in continuous mode (any dual mode), the ADC keeps running, and writing the converted value for ADC1 and ADC2 in the DR register of ADC1. At the end of each conversion, it sets the EOC bit in register SR, and that in turns tells the DMA to read the content of register ADC1->regs->DR and transfer it to memory. In the case of the O-Scope, the ADC is always running and sampling, and when DMA is enabled in the takeSamples() function, it starts transferring right away. Since DR always contains the same 2 channels (one for ADC1, one for AC2), it works.

When you have a sequence of channels to be sampled, if you use the same approach, the DMA transfer starts as soon as you enable the DMA. Problem is, the ADC is always running, and you don’t know what channels are in the DR register at that time. If like me you have 4 channels (IN8 and IN6 on ADC1, IN7 and IN5 on ADC2), you can start the memory transfer either when IN8 and IN7 are there, or when IN6 and IN5 are there. That’s what was happening to me.

The trick is to make sure that the ADC is not running, enable DMA, then start the ADC only when needed. The DMA transfer will wait patiently until the ADC starts, then upon the first EOC will start transferring from the right set of channels (IN8 and IN7 in my case).

It turns out, though, it’s tricky to set the ADC in continuous mode and ensure that the ADC is not running (probably due to the Maple libraries already having enabled the ADC).

Doing something like:

ADC1->regs->CR2 = ( ADC_CR2_ADON | ADC_CR2_CONT | ADC_CR2_DMA | ADC_CR2_EXTSEL | ADC_CR2_EXTTRIG);

Turns out to be a bad idea, while

ADC1->regs->CR2 = ( ADC_CR2_CONT | ADC_CR2_DMA | ADC_CR2_EXTSEL | ADC_CR2_EXTTRIG);
ADC1->regs->CR2 |= ADC_CR2_ADON;

Works fine. There’s a cryptic note in the reference manual, 11.12.3. It says “If any other bit in this register apart from ADON is changed at the same time, then conversion is not triggered. This is to prevent triggering an erroneous conversion”. Well, it turns out that changing a bunch of bits together while changing ADC_CR2_ADON doesn’t stop a conversion either (and this was a real puzzler for the longest time). When I enabled ADC_CR2_CONT, the ADC was already enabled and even if the ADC_CR2_SWSTART bit was not set, started running. When later on I thought I was starting conversion using ADC_CR2_SWSTART, made no difference (since it was already running), and that’s why the DMA transfer started happening randomly (seemingly randomly, I can now say).

So the trick is to set the CR2 register while explicitly disabling the ADC. That stops everything. Then enable just ADC_CR2_CONT. At this point the ADC is “armed” and waiting for ADC_CR2_SWSTART to actually start the continuous conversion.

So, assuming the ADC1 and ADC2 registers are set in your init code, the right order to start a new conversion is:

  • Disable the ADC, and set the CR2 register as needed
    Enable the ADC
    Set the DMA
    (do whatever you need to do to decide when the conversion is ready to start, trigger or otherwise)
    Set ADC_CR2_SWSTART to finally get the conversion going

In my case it’s critical to start converting all 4 channels as close as possible to the trigger, because in order to cross correlate various sound waves, the initial slope is pretty critical (and likely the strongest part of the sound impulse for impulsive sounds). It might be critical for an oscilloscope in order to capture a single high speed signal (but, then again, you can run quite a few ARM instructions in a CPU running at 72MHz, maybe I worry too much)

Here’s the working code:

/* MapleDualADC - A sample to perform dual ADC conversions using DMA */
//
// As written it performs a total of NUM_SAMPLES dual conversions (ADC1 and ADC2) on Maple Mini
// continuosly scanning the defined channels (D3->IN8 and D5->IN7 in the first conversion
// followed by D4->IN6 and D6->IN5 on the second conversion). In the example, repeated 4 times
// NUM_SAMPLES=(number INx channels converted/2) * number of times each channel is scanned
/*
Copyright (c) 2015 Roberto Cazzaro. All rights reserved.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
*/

#include <libmaple/dma.h>

#define CHANNELS_PER_ADC 2 // number of channels for each ADC. Must match values in ADCx_Sequence array below
#define NUM_SAMPLES 8 // number of samples for each ADCx. Each channel will be sampled NUM_SAMPLES/CHANNELS_PER_ADC
#define SAMPLE_RATE ADC_SMPR_71_5 // when using dual mode, each pair of channels must have same rate. Here all channels have the same

uint32 adcbuf[NUM_SAMPLES+1]; // buffer to hold samples, ADC1 16bit, ADC2 16 bit
uint8 ADC1_Sequence[]={8,6,0,0,0,0}; // ADC1 channels sequence, left to right. Unused values must be 0. Note that these are ADC channels, not pins
uint8 ADC2_Sequence[]={7,5,0,0,0,0}; // ADC2 channels sequence, left to right. Unused values must be 0

// calculate values for SQR3. Function could be extended to also work for SQR2 and SQR1. As is, you can sequence only 6 sequences per ADC
uint32 calc_adc_SQR3(uint8 adc_sequence[6]){
int SQR3=0;

for (int i=0;i<6;i++) // There are 6 available sequences in SQR3 (SQR2 also 6, and 4 in SQR1).
{
//each sequence is 5 bits
SQR3 |= adc_sequence[i] << ((i*5));
}
return SQR3;
}

// initialize DMA1, Channel1 (ADC)
void set_dma() {
dma_init(DMA1);
dma_setup_transfer(DMA1, DMA_CH1, &ADC1->regs->DR, DMA_SIZE_32BITS,
adcbuf, DMA_SIZE_32BITS, DMA_MINC_MODE);
dma_set_num_transfers(DMA1, DMA_CH1,NUM_SAMPLES);
dma_set_priority(DMA1, DMA_CH1, DMA_PRIORITY_VERY_HIGH);

dma_enable(DMA1, DMA_CH1);
}

// calibrate ADC1 and ADC2, then set all registers for regular simultaneous dual ADC conversion with DMA transfer
void set_adc() {
adc_set_sample_rate(ADC1, SAMPLE_RATE); // sets sample rate for all channels, all identical (critical in dual mode, even if only pairs sampled together need to be identical)
adc_set_sample_rate(ADC2, SAMPLE_RATE);

adc_calibrate(ADC1);
adc_calibrate(ADC2);

ADC1->regs->CR1 = 1 << 8; // Set scan mode
ADC1->regs->CR1 |= 6 << 16; //Regular simultaneous mode. Required for ADC1 only, ADC2 is slave
ADC1->regs->CR2 = ( ADC_CR2_CONT | ADC_CR2_DMA | ADC_CR2_EXTSEL | ADC_CR2_EXTTRIG); //0xe0102; cont conversion, DMA, right aligned, disable external; EXTSEL, exttrig=0,jswstart=0

adc_set_reg_seqlen(ADC1, CHANNELS_PER_ADC); // how many channels per ADC
//ADCx->regs->SQR3-1 holds the sequence of channels to convert. A conversion function is provided calc_adc_sequence(ADC1_Sequence) only for SQR3.
//If more than 6 channels are needed, repeat the same for SQR2 and SQR1 (SQR1 only holds 4 sequences)
ADC1->regs->SQR3 |= calc_adc_SQR3(ADC1_Sequence); //200; for IN8 and IN6, on Maple Mini these are D3, D5

ADC2->regs->CR1 = 1 << 8; // Set scan mode
ADC2->regs->CR2 = ( ADC_CR2_CONT | ADC_CR2_EXTSEL | ADC_CR2_EXTTRIG); //0xe0003;

adc_set_reg_seqlen(ADC2, CHANNELS_PER_ADC);
ADC2->regs->SQR3 |= calc_adc_SQR3(ADC2_Sequence); //= 167 forIN7 and IN5, on Maple Mini these are D4, D6 respectively

ADC1->regs->CR2 |= ADC_CR2_ADON; // it is critical to enable ADC (bit 0=1) independently of all other changes to the CR2 register
ADC2->regs->CR2 |= ADC_CR2_ADON; // enabling all at once (i.e. ADC_CR2_ADON | ADC_CR2_CONT | ADC_CR2_EXTSEL ) will cause problems when used with continuous mode
}

// set only the registers that need to be reset before each conversion
void adc_to_ready() {
ADC1->regs->CR2 = ( ADC_CR2_CONT | ADC_CR2_DMA | ADC_CR2_EXTSEL | ADC_CR2_EXTTRIG); //0xe0102; cont conversion, DMA, right aligned, disable external; EXTSEL, exttrig=0,jswstart=0
ADC2->regs->CR2 = ( ADC_CR2_CONT | ADC_CR2_EXTSEL | ADC_CR2_EXTTRIG); //0xe0002;
ADC1->regs->CR2 |= ADC_CR2_ADON; // it is critical to enable ADC (bit 0=1) independently of all other changes to the CR2 register
ADC2->regs->CR2 |= ADC_CR2_ADON; // enabling all at once (i.e. ADC_CR2_ADON | ADC_CR2_CONT | ADC_CR2_EXTSEL ) will cause problems when used with continuous mode
}

// check for DMA transfer finished, resetting isr bit
uint8 dma_transfer_finished() {
if(dma_get_isr_bits(DMA1,DMA_CH1)==0x07) {
int result=dma_get_irq_cause(DMA1,DMA_CH1);//<--clears isr bits
return 1;
}
return 0;
}

void setup() {
Serial.begin(115200); // Ignored by Maple. But needed by boards using Hardware serial via a USB to Serial Adaptor

pinMode(3,INPUT_ANALOG); //set all used pins as INPUT_ANALOG, in the example 3,4,5,6
pinMode(4,INPUT_ANALOG);
pinMode(5,INPUT_ANALOG);
pinMode(6,INPUT_ANALOG);

set_adc(); // initial ADCa and ADC2 settings
}

void loop() {
adc_to_ready(); // it is critical to set ADC before DMA, to avoid partial DMA transfers being stored. If the ADC is in continuous mode while DMA is being set
set_dma(); // the DMA transfer will be triggered and the DMA counter will start from the wrong value

ADC1->regs->CR2 |= ADC_CR2_SWSTART; //start conversion, STM32 will reset this bit immediately. Only ADC1 (master) needs to be started

while(!dma_transfer_finished());
dma_disable(DMA1,DMA_CH1); //stop dma transfer

for(int i=0;i<(NUM_SAMPLES);i++) {
Serial.print(i);
Serial.print(" ");
Serial.print(adcbuf[i] & 0xFFFF);
Serial.print(" - ");
Serial.println((adcbuf[i] & 0xFFFF0000) >>16);
}
Serial.println();

delay(750);
}

robca Wed Nov 25, 2015 3:38 pm

ahull wrote:
If a few other eyes could check my figures, and assuming they stack up, we can then see how to try to implement this.

robca Wed Nov 25, 2015 5:09 pm

Roger, out of curiosity, do you know why I need this line:

#include <libmaple/dma.h>

in my code to ensure that I can properly access the DMA registers, but I do not need the equivalent for the ADC? Did I setup my Arduino environment wrong? I used the installation steps you list here https://github.com/rogerclarkmelbourne/ … stallation

Not a real issue, just curious to understand better how the STM32duino libraries work

RogerClark Wed Nov 25, 2015 9:11 pm

@robca

Libmaple is the core, not a library.

We never changed the name that was inherited from the code written by Leaflabs for their Maple and Maple mini products.

you would need to include dma.h if you want to use any of the built in dma functions, but it sounds like what you need to accomplish would require various settings of the dma registers which are more complex than the basic stuff in the core dma code

robca Wed Nov 25, 2015 11:51 pm

Sorry, I misspoke, I meant “core”, not library… thanks for correcting the terminology.

My question still stands, though: if I want to use direct registry access for the DMA, I need to add libmaple\dma.h. I’m using direct DMA registry access (because as you say it’s the only way to really manipulate the registers: built-in functions are good for standard stuff, but once you understand how the DMA/ADC works, direct registry access is faster and more flexible). So I use things like DMA1->regs->CCR1

But I also use things like ADC1->regs->CR2, and that works without any include… I guess I’m just curious to understand why ADC1 and ADC1 work, while DMA1 requires an include. Honestly, not that important, just curious :)

ahull Thu Nov 26, 2015 12:44 am

I think I see *how* adc.h doesn’t need included…
.../Arduino_STM32$ grep -r include |grep adc.h |grep f1

STM32F1/system/libmaple/include/libmaple/adc.h:#include "stm32f1/include/series/adc.h"
STM32F1/system/libmaple/stm32f1/include/series/adc.h: * included in all copies or substantial portions of the Software.
STM32F1/system/libmaple/stm32f1/include/series/adc.h: * @file libmaple/stm32f1/include/series/adc.h
STM32F1/system/libmaple/stm32f1/include/series/adc.h:#include <libmaple/bitband.h>
STM32F1/system/libmaple/stm32f1/include/series/adc.h:#include <libmaple/libmaple_types.h>
STM32F1/system/libmaple/stm32f1/include/series/adc.h:#include <libmaple/rcc.h> /* For the prescalers */
STM32F1/cores/maple/stm32f1/util_hooks.c:#include <libmaple/adc.h>
STM32F1/cores/maple/libmaple/adc_f1.c:#include <libmaple/adc.h>

Sign in to reply