stm32-rs / stm32f1xx-hal

A Rust embedded-hal HAL impl for the STM32F1 family based on japarics stm32f103xx-hal
Apache License 2.0
565 stars 177 forks source link

Problem with runtime-configurable continuous ADC acquisition with DMA #484

Open wzab opened 5 months ago

wzab commented 5 months ago

I'm writing a firmware where the ADC should be runtime configured for sampling the selected set of inputs (configured as regular sequence) with the selected frequency. Data should be transferred via DMA, and the measurement should be triggered via a timer.

I have found that the current API is extremely inconvenient for that. For with_scan_dma, I need to provide my own implementation of SetChannels (example here). However that implementation does not allow using runtime-configurable list of inputs. I have worked it arround implementing code like this:

static ADC_CHANS : Vec<u8, { sizes::MAX_CHANNELS }> = Vec::new();
...
pub struct AdcPins {}

impl SetChannels<AdcPins> for Adc<ADC1> {
    fn set_samples(&mut self) {
        for v in &crate::ADC_CHANS {
        self.set_channel_sample_time(*v, adc::SampleTime::T_28);
        }
    }
    fn set_sequence(&mut self) {
        self.set_regular_sequence(crate::ADC_CHANS.as_slice());
        // Optionally we can set continuous scan mode
        self.set_continuous_mode(true);
        // Also we can use discontinuous conversion (3 channels per conversion)
        self.set_discontinuous_mode(Some(u8::try_from(crate::ADC_CHANS.len()).unwrap()));
    }
}

The sampling is delegated to the async software task in rtic v2. The ADC and DMA is initialized in init, and passes as Locale to that task.

        //Initialization in init
        let mut dma_ch1 = ctx.device.DMA1.split().1;
        let mut adc1 = adc::Adc::adc1(ctx.device.ADC1, clocks);

Than the task is running a loop where it receives the CONFIGURE, START, and STOP command. After START it configures the ADC and DMA, starts the transfer and waits for STOP. However, the problem is that I can't perform proper configuration due to ownership problems:

                     // Configure ADC and DMA
                        adc1.with_scan_dma(AdcPins {}, dma_ch1);

The code above generates:

error[E0308]: mismatched types
   --> src/main.rs:233:56
    |
233 |                         adc1.with_scan_dma(AdcPins {}, dma_ch1);
    |                              -------------             ^^^^^^^ expected `C1`, found `&mut C1`
    |                              |
    |                              arguments to this method are incorrect

If i modify it to:

                        // Configure ADC and DMA
                        adc1.with_scan_dma(AdcPins {}, *dma_ch1);

I get:

error[E0507]: cannot move out of `*adc1` which is behind a mutable reference
   --> src/main.rs:233:25
    |
233 |                         adc1.with_scan_dma(AdcPins {}, *dma_ch1);
    |                         ^^^^ ----------------------------------- `*adc1` moved due to this method call
    |                         |
    |                         move occurs because `*adc1` has type `Adc<ADC1>`, which does not implement the `Copy` trait
    |
note: `Adc::<ADC1>::with_scan_dma` takes ownership of the receiver `self`, which moves `*adc1`
   --> /home/emb/.cargo/registry/src/index.crates.io-6f17d22bba15001f/stm32f1xx-hal-0.10.0/src/adc.rs:814:1
    |
814 | / adcdma! {
815 | |     pac::ADC1: (
816 | |         AdcDma1,
817 | |         dma1::C1,
818 | |     )
819 | | }
    | |_^
    = note: this error originates in the macro `adcdma` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0507]: cannot move out of `*dma_ch1` which is behind a mutable reference
   --> src/main.rs:233:56
    |
233 |                         adc1.with_scan_dma(AdcPins {}, *dma_ch1);
    |                                                        ^^^^^^^^ move occurs because `*dma_ch1` has type `stm32f1xx_hal::dma::dma1::C1`, which does not implement the `Copy` trait

I can work it around, by passing the reference to the RegisterBlock for ADC and DMA to the data acquisition task, like done (for STM32F3...) in this example. Certain modifications for porting from STM32F3... to STM32F1... are needed, but generally this approach looks promissing. At least the below initialization does not lead to errors:

        //Initialize ADC and DMA to make sure that proper clocks are enabled
        let mut dma_ch1 = ctx.device.DMA1.split().1;
        let mut adc1 = adc::Adc::adc1(ctx.device.ADC1, clocks);

        //Prepare the register blocks to be passed as `Local` to the data acquiring task
        let r_adc1 = unsafe { &mut *(ADC1::ptr() as *mut _) };
        let r_dma1 = unsafe { &mut *(DMA1::ptr() as *mut _) };