STM32 ADC read with DMA doesn't preserve channel order after restart
I am trying to configure an ADC with DMA for reading samples from some sensors on a custom PCB using an STM32F4-family chip. I followed the ADC DMA example and configured the ADC sample sequence / channels like so:
adc.set_sample_sequence(
Sequence::One,
&mut peripherals.analog_pin_1,
SampleTime::CYCLES112,
); // Sensor 1 at pin_1
adc.set_sample_sequence(
Sequence::Two,
&mut peripherals.analog_pin_2,
SampleTime::CYCLES112,
); // Sensor 2 at pin_2
I then call adc.read() in a loop from a high-priority interrupt executor. I would expect that the buffer I pass to adc.read() contains samples in the order [PIN1, PIN2, PIN1, PIN2, ...], but instead the order keeps changing every time I call adc.start(). Sometimes it is the expected order, sometimes it is reversed ([PIN2, PIN1, PIN2, PIN1, ...]).
The documentation however states that the order should be preserved:
"Each call to read will populate the measurements array in the same order as the channels defined with set_sample_sequence. There will be many sequences worth of measurements in this array because it only returns if at least half of the DMA buffer is filled. For example if 3 channels are sampled measurements contain: [sq0 sq1 sq3 sq0 sq1 sq3 sq0 sq1 sq3 sq0 sq1 sq3..]." (from RingBufferedAdc::read)
The reason I am calling adc.start() (and adc.teardown_adc()) is that in the documentation of adc.read() it states that I can control the sample rate by manually starting and stopping the DMA transfer:
"To control the sample rate, call teardown_adc after each readout, and then start the DMA again at the desired interval"
It seems to me that restarting the DMA transfer does not preserve the order anymore, perhaps because it does not reset the position in the internal ring-buffer from which the next samples will be read.
I'm having this exact problem too, on stm32f722re
Maybe it's because start first sets up the adc(starting the ring buffer) and then clears the ring buffer? Instead of first clearing it and then starting the continuous transfer.
/// Turns on ADC if it is not already turned on and starts continuous DMA transfer.
pub fn start(&mut self) -> Result<(), OverrunError> {
self.setup_adc();
self.ring_buf.clear();
Ok(())
}
fn setup_adc(&mut self) {
compiler_fence(Ordering::SeqCst);
self.ring_buf.start();
let r = T::regs();
// Enable ADC
...
}
I tried but it didn't work
@Mortano what did you end up using for the ADC?
@tommasoclini I used the blocking ADC implementation as a workaround because I don't have a lot of data to read. Not ideal but acceptable for my usecase.
@Dirbaio do you have any idea of what the problem could be?
I'm having the exact same problem on an stm32f439zi. For the moment I am working around the issue by just repeating some measurements so that I have a repeating pattern I can search for to find the random offset every time. Sometimes this fails to find the offset with confidence and then just throws away the values and tries again. This is a very ugly hack that works for my use case but probably not for many others.