0

I’m trying to implement ADC conversion from gpio analog input at the sample rate of 192 kHz. Then I want to send the array of samples over tcp to the client.

My question:

How to implement ADC convertion using DMA for STM32F767ZI?

There is such example for stm32f4 rtic-adc-dma

but for stm32f7 it must be done in a slightly different way depending on stm32f7 hal implementation

Already tried

  • ADC in blocking mode (polling)
    It works, but very slowly and main CPU used for conversion, using DMA cpu can be used for other tasks like transferring and so on.
  • by @JishanShaikh was suggested:
#![no_std]
#![no_main]

use core::{cell::UnsafeCell, mem::MaybeUninit};

use panic_semihosting as _;
use cortex_m_semihosting::hprintln;

use cortex_m::asm;
use cortex_m_rt::entry;
use heapless::spsc::{Queue};
use stm32f7xx_hal::{
    interrupt,
    adc::{
        Adc, ChannelTimeSequence, StoredConfig,
    },
    pac::{DMA2, ADC1},
    dma::{self, DMA},
    gpio::{Analog, gpioa::PA3},
    prelude::*,
    rcc::RccExt,
};

const QSIZE: usize = 16;
const QSIZE_ADD: usize = QSIZE + 1;
static mut QUEUE: Queue<u16, QSIZE_ADD> = Queue::new();
static mut BUFFER: CircBuffer<u16, QSIZE> = CircBuffer::new();

#[entry]
fn main() -> ! {
    let cp = cortex_m::Peripherals::take().unwrap();
    let dp = stm32f7xx_hal::pac::Peripherals::take().unwrap();

    let rcc = dp.RCC.constrain();
    let clocks = rcc.cfgr.sysclk(216.MHz()).freeze();

    let dma = DMA::new(dp.DMA2);

    // i was found only StoredConfig implementation in the stm32f7xx_hal
    // let adc_conf = StoredConfig::default();
    let mut adc = Adc::adc1(
        dp.ADC1,
        &mut rcc.apb2,
        &clocks,
        12,
        true, 
    );  

    let gpio_a = dp.GPIOA.split();
    let pa3 = gpio_a.pa3.into_analog();

    let _circ_buffer = configure_adc_dma(dma, adc, pa3);

    let mut cnt = 0_usize;
    let q = unsafe {&mut QUEUE};
    loop {
        cortex_m::asm::wfi();
        let mut buf = [0; QSIZE];
        let len = q.len();
        for i in 0..len {
            buf[i] = q.dequeue().unwrap();
        }
        cnt += len;
        if cnt >= QSIZE {
            hprintln!("[main] buf: {:?}", buf);
            cnt = 0;
        }
    }
}


#[interrupt]
fn DMA2_STREAM0() {

    if let buffer = unsafe {&mut BUFFER} {  // BUFFER.as_mut()
        buffer.read(|sample, _| {
            let q = unsafe {&mut QUEUE};
            q.enqueue(*sample).unwrap();
        });
        buffer.release(1);
    }

    unsafe { (*DMA2::ptr()).lifcr.write(|w| w.ctcif0().set_bit()) };
}

fn configure_adc_dma(
    dma: DMA<DMA2>,
    adc: Adc<ADC1>,
    // adc: Adc<stm32f7xx_hal::pac::ADC1>,
    pa3: PA3<Analog>,
) -> &'static mut CircBuffer<u16, QSIZE> {
    let buffer= unsafe {&mut BUFFER};

    let mut channel = adc.channel(AdcChannel::Adc3, Default::default());
    channel.enable_dma();
    let mut dma_ch = dma.channel0
            .mem2mem()
            .circular()
            // .set_memory_size(dma::MemorySize::HalfWord)
            // .set_peripheral_size(dma::MemorySize::HalfWord)
            .set_memory_increment(true)
            .set_peripheral_increment(false)
            // .set_direction(dma::Direction::PeripheralToMemory)
            .set_peripheral_address(&adc.adr)
            .set_memory_address(buffer.peek().unwrap())
            .set_number_of_data(QSIZE as u16);
    dma_ch.start();

    // cortex_m::interrupt::free(|_| unsafe { BUFFER.replace(buffer) });
    // cortex_m::interrupt::free(|_| unsafe { BUFFER.replace(circ_buffer) });

    dma_ch.listen(dma::Event::TransferComplete);

    adc.set_continuous_mode(true);
    adc.cr2.modify(|_, w| w.cont().continuous());
    adc.cr2.modify(|_, w| w.adon().enabled());

    pa3.into_analog();

    dma_ch.enable();

    buffer
    // circ_buffer
}

/// Some circular buffer
struct CircBuffer<T, const N: usize> {
    buffer: [UnsafeCell<MaybeUninit<T>>; N],
}
impl<T, const N: usize> CircBuffer<T, N> {
    const INIT: UnsafeCell<MaybeUninit<T>> = UnsafeCell::new(MaybeUninit::uninit());
    pub const fn new() -> Self {
        Self {
            buffer: [Self::INIT; N],
        }
    }
    pub fn read(self, callback: fn(&u16, usize)) {
        (callback)(&0_u16, 0)
    }
    pub fn peek(&self) -> Option<&T> {
        Some(unsafe { &*(self.buffer.get_unchecked(0).get() as *const T) })
    }
    pub fn release(self, v: usize) {}
}

Seems like stm32f7xx_hal, doesn't implements fallowing:

  • adc.channel(AdcChannel::Adc3, Default::default())
  • dma.channel0.mem2mem().circular() ....
  • dma_ch.listen(dma::Event::TransferComplete);
  • dma::Event

How can i fix it ?

  • If I understand correctly you have benchmarked and most of the time is spent on adc.convert? – Orión González May 02 '23 at 11:01
  • i haven't benchmarked, but i want to have an asynchronous ADC conversion and get the interrupt on conversion/half completed – anton lobanov May 02 '23 at 12:18
  • In blocking mode, the main CPU is busy waiting for each conversion to complete before moving on to the next; That is why it is slow. Maybe implementing DMA interrupt like [this](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b23f27308b0f1c3fe3261072787ffd42)? – Jishan Shaikh May 02 '23 at 12:44
  • @JishanShaikh thank you for your example, i'll try to use it... but it seems like stm32f7xx_hal, doesn't implements adc::config, adc::AdcChannel, dma::CircBuffer, dma::MemorySize, dma::Event yet – anton lobanov May 02 '23 at 13:51

0 Answers0