stm32-rs / stm32f4xx-hal

A Rust embedded-hal HAL for all MCUs in the STM32 F4 family
BSD Zero Clause License
573 stars 213 forks source link

The ADC does not work in continuous mode if there is a small delay after starting #799

Closed faunel closed 1 month ago

faunel commented 2 months ago

The ADC does not work in continuous mode if there is a small delay after starting (transfer.start) A delay of 1 ms is sufficient (line delay.delay_ms(1);).

I tested this on two boards - STM32F401CE, STM32F411CE (Black pill) And the issue occurs on both.

Also, the ADC can suddenly stop working if there is a blocking operation in the task for a few milliseconds during the process. There are no errors in this case.

Also, in single mode, if we start the ADC in the interrupt handler, the ADC can also suddenly stop working. This manifests at a start frequency of 100 kHz, Pclk2_div_2, and a small number of cycles (Cycles_112 or less).

#[init]
    fn init(cx: init::Context) -> (Shared, Local) {
        let dp = cx.device;
        let cp = cx.core;

        // Clock configuration
        let clocks = dp
            .RCC
            .constrain()
            .cfgr
            .use_hse(25.MHz())
            .sysclk(80.MHz())
            .freeze();

        // GPIO
        let gpioa = dp.GPIOA.split();

        let mut delay = cp.SYST.delay(&clocks);

        // ADC pin configuration
        let adc_pin1 = gpioa.pa1.into_analog();
        let adc_pin2 = gpioa.pa2.into_analog();

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

        // ADC config
        let adc_config = AdcConfig::default()
            .dma(Dma::Continuous)
            .scan(Scan::Enabled)
            .clock(Clock::Pclk2_div_8)
            .continuous(Continuous::Continuous);

        // ADC channel configuration
        let mut adc = Adc::adc1(dp.ADC1, true, adc_config);
        adc.configure_channel(&adc_pin1, Sequence::One, SampleTime::Cycles_480);
        adc.configure_channel(&adc_pin2, Sequence::Two, SampleTime::Cycles_480);

        let first_buffer = cortex_m::singleton!(: [u16; 2] = [0; 2]).unwrap();
        let buffer = Some(cortex_m::singleton!(: [u16; 2] = [0; 2]).unwrap());

        // DMA config
        let dma_config = DmaConfig::default()
            .transfer_complete_interrupt(true)
            .memory_increment(true)
            .double_buffer(false);

        let mut transfer =
            Transfer::init_peripheral_to_memory(dma.0, adc, first_buffer, None, dma_config);

        transfer.start(|adc| {
            adc.start_conversion();
        });

        // It does not work with this delay.
        delay.delay_ms(1);

        (
            Shared {
                transfer
            },
            Local {
                buffer
            },
        )
    }

Added a project with an example in the attachment. adc_bug.zip

faunel commented 1 month ago

I don't think this is a library error. The ADC stops when data from the buffer is not retrieved in time and it overflows. In this case, an Overrun error is set in the register. To solve this problem, the following steps should be followed:

Set the minimum ADC speed (SampleTime::Cycles_480, Clock::Pclk2_div_8). Optimize the code to retrieve data from the DMA buffer as quickly as possible. If a faster ADC is needed, a second DMA buffer can be used by enabling it. This will allow data to be retrieved faster and avoid overflow. For example, you can set Cycles_56 and everything will work.

However, in any case, the DMA interrupt handler must have the highest priority, and the code must be optimized to not interfere with this handler's quick operation, for example, by not blocking it in other tasks when using RTIC. The main goal is to retrieve data from the DMA buffer as quickly as possible to prevent it from overflowing.