I'm looking to get my STM32F303 discovery board to continuously read from a temperature sensor using the ADC and DMA transfers to a memory location, but during the interrupt callback, the memory location appears to only have been written to once and is never updated subsequently. In the callback, I also confirm that the value in the ADC's data register is changing.

I'm at a loss as to what the issue here is and could use some pointers in the right direction. I'm using the Knurling app template, RTIC (Real-Time Interrupt-driven Concurrency) and the stm32f3xx-hal crate for this project.

Here's my current source code

#![no_main]
#![no_std]

use cortex_m::{asm, peripheral::NVIC};
use stm32f3xx_hal::{pac::{self, Interrupt}, rcc::Clocks};
use stm32f3xx_hal::prelude::*;

use adc_app as _; // global logger + panicking behavior + memory layout

#[rtic::app(device = pac, peripherals = true)]
mod app {
    use super::*;

    #[shared]
    struct Shared {}

    #[local]
    struct Local {
        adc1: &'static mut pac::adc1::RegisterBlock,
        dma1: &'static mut pac::dma1::RegisterBlock,
        adc_buffer: [u16; ADC_BUFFER_LENGTH],
    }

    const ADC1_ADDRESS: u32 = 0x5000_0000;
    const ADC1_DR_ADDRESS_OFFSET: u32 = 0x40;
    const ADC_BUFFER_LENGTH: usize = 4;
    const CLOCK_HZ: u32 = 12_000_000;
    const MAX_ADVREGEN_STARTUP_US: u32 = 10;

    const SAMPLE_HZ: u32 = 8_000;
    const ARR_PERIOD: u32 = CLOCK_HZ / SAMPLE_HZ; // number of periods in SAMPLE_HZ required to auto-reload register (arr)

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

        init_rcc(&dp.RCC);

        let mut flash = dp.FLASH.constrain();
        let mut rcc = dp.RCC.constrain();
        let clocks = rcc.cfgr.sysclk(CLOCK_HZ.Hz().into()).freeze(&mut flash.acr);

        let mut gpioa = dp.GPIOA.split(&mut rcc.ahb);
        gpioa.pa0.into_analog(&mut gpioa.moder, &mut gpioa.pupdr);

        init_tim2(&dp.TIM2);

        init_adc(&dp.ADC1, &dp.ADC1_2, &clocks);

        let adc_buffer = init_dma(&dp.DMA1);

        (
            Shared {},
            Local {
                adc1: unsafe { &mut *(pac::ADC1::ptr() as *mut _) },
                dma1: unsafe { &mut *(pac::DMA1::ptr() as *mut _) },
                adc_buffer,
            },
            init::Monotonics(),
        )
    }

    #[idle]
    fn idle(_: idle::Context) -> ! {
        loop {}
    }

    #[task(binds = DMA1_CH1, local = [adc1, dma1, adc_buffer])]
    fn dma1_ch1(ctx: dma1_ch1::Context) {
        let dma1_ch1::LocalResources { adc1, adc_buffer, dma1, .. } = ctx.local;
        let isr = dma1.isr.read();
        dma1.ifcr.write(|w| w
            .chtif1().clear()
            .ctcif1().clear()
            .cteif1().clear()
        );

        let bits = adc1.dr.read().rdata().bits();
        // handle interrupt events
        if isr.htif1().is_half() {
            defmt::info!("interrupt: half: {:?}: {}", adc_buffer, bits);
        } else if isr.tcif1().is_complete() {
            defmt::info!("interrupt: full: {:?}: {}", adc_buffer, bits);
        } else if isr.teif1().is_error() {
            // handle dma error
            defmt::error!("interrupt: dma transfer error");
        } else {
            // handle unknown interrupt
            defmt::error!("interrupt: unknown error");
        }
    }

    // Initialize all rcc configurations
    fn init_rcc(rcc: &pac::RCC) {
        // set the division factor of the AHB clock to be SYSCLK / 1
        rcc.cfgr.modify(|_, w| w.hpre().div1());

        // configure AHB clock
        rcc.ahbenr.modify(|_, w| w
            .adc12en().enabled() // enable the ADC1/2 clock
            .dma1en().enabled()  // enable the DMA clock
        );

        // enable the APB1 clock to the TIM2 timer
        rcc.apb1enr.modify(|_, w| w.tim2en().enabled());
    }

    // Initialize the TIM2 timer
    fn init_tim2(tim2: &pac::TIM2) {
        // trigger interrupt when counter reaches arr value
        tim2.cr2.write(|w| w.mms().update());

        // timer period
        tim2.arr.write(|w| w.arr().bits(ARR_PERIOD));

        // enable TIM2
        tim2.cr1.modify(|_, w| w.cen().enabled());
    }

    // Configure ADC1 on channel 1 to initiate conversion, where
    // the timing is connected to the DMA clock TIM2.
    //
    // T_ADC = num conversions in sequence * (num clock cycles in SMPL + num resolution bits + 0.5) * T_ADC_CLK
    // T_External_trigger >= (T_ADC / T_ADC_CLK + 1) * T_ADC_CLK
    //
    // See section 15.3.10 Constraints when writing the ADC control bits
    fn init_adc(adc1: &pac::ADC1, adc1_2: &pac::ADC1_2, clocks: &Clocks) {
        // disable ADC prior to configuration / callibration
        adc1.cr.modify(|_, w| w.aden().clear_bit());

        // ADC clock will be derived from the AHB clock divided by 4
        adc1_2.ccr.modify(|_, w| w.ckmode().sync_div4());

        // enable the ADC voltage regulator
        adc1.cr.modify(|_, w| w.advregen().intermediate());
        adc1.cr.modify(|_, w| w.advregen().enabled());

        // wait for maximum possible necessary start up time (10us) of ADC voltaget regulator
        asm::delay(MAX_ADVREGEN_STARTUP_US * 1_000_000 / clocks.sysclk().0);

        // set ADC channel 1 (difsel_10 is intentional) to be in single-ended input mode (uses V_REF- as reference voltage)
        adc1.difsel.modify(|_, w| w.difsel_10().clear_bit());
        adc1.cr.modify(|_, w| w.adcaldif().single_ended());

        // start ADC calibration
        adc1.cr.modify(|_, w| w.adcal().calibration());

        // wait for ADC calibration to finish
        while adc1.cr.read().adcal().is_calibration() {}

        // wait 8 * 4 clock cycles of the AHB
        asm::delay(8 * 4);


        // enable ADC
        adc1.cr.modify(|_, w| w.aden().enable());
        // wait until ADC is ready
        while adc1.isr.read().adrdy().is_not_ready() {}


        // set number of conversions per sequence to 1 and the conversion to be from channel 1 (sq1)
        adc1.sqr1.modify(|_, w| unsafe { w
            .l().bits(0)   // set number of conversions per sequence to 1
            .sq1().bits(1) // set first conversion to occur on channel 1
        });

        // set the ADC sampling time to be longer than the required time for a correct sample to
        // be taken given an expected external source output impedance
        adc1.smpr1.modify(|_, w| w.smp1().bits(0));

        adc1.cfgr.modify(|_, w| w
            .cont().continuous()   // set mode to continuous conversion
            .discen().disabled()   // disable discontinous mode
            .dmacfg().circular()   // enable DMA to operate in cicular mode (suitable for streaming input data)
            .dmaen().enabled()     // enable DMA (transfers will be triggered on each conversion)
            .exten().rising_edge() // set conversions to occur on rising edges
            .extsel().tim2_trgo()  // set conversion to be triggered by TIM2_TRGO event
            .res().bits12()        // set bit resolution to 12
        );


        // --- Start continuous ADC conversion ---

        // start ADC conversion
        adc1.cr.modify(|_, w| w.adstart().start());
    }

    // Initialize the DMA1 controller
    fn init_dma(dma1: &pac::DMA1) -> [u16; ADC_BUFFER_LENGTH] {
        let adc_buffer = [0x00_u16; ADC_BUFFER_LENGTH];

        // set the source address: buffer address
        dma1.ch1.mar.modify(|_, w| unsafe { w.ma().bits(adc_buffer.as_ptr() as u32) });

        // set the destintaion address: ADC1 12-bit right-aligned data holding register (ADCx_DR)
        dma1.ch1.par.modify(|_, w| unsafe { w.pa().bits(ADC1_ADDRESS + ADC1_DR_ADDRESS_OFFSET) });

        // set the number of items to be transferred (item size is configured below)
        dma1.ch1.ndtr.modify(|_, w| w.ndt().bits(ADC_BUFFER_LENGTH as u16));

        // configure DMA channel to be used as a double buffer
        // i.e. while half the buffer is transferred, the other
        // half is being filled
        dma1.ch1.cr.modify(|_, w| w
            .mem2mem().disabled()    // channel is memory to peripheral so mem2mem is disabled
            .pl().high()             // set the channel priority (pl) to high
            .msize().bits16()        // set memory word size to 16 bits
            .psize().bits16()        // set peripheral word size to 16 bits
            .minc().enabled()        // increment memory address after every transfer
            .pinc().disabled()       // disable peripheral increment mode
            .circ().enabled()        // dma mode is circular
            .dir().from_peripheral() // transfer data from peripheral to memory
            .htie().enabled()        // enable half transfer interrupts
            .tcie().enabled()        // enable transfer complete interrupts
            .teie().enabled()        // enable transfer error interrupts
        );

        // enable DMA interrupt
        unsafe {
            NVIC::unmask(Interrupt::DMA1_CH1);
        }

        // enable DMA channel
        dma1.ch1.cr.modify(|_, w| w.en().enabled());

        defmt::info!("dma setup: {:?}", adc_buffer);

        asm::delay(400);

        adc_buffer
    }
}

Here's some sample output from this run

0 INFO  dma setup: [1044, 1046, 1040, 1041]
└─ main::app::init_dma @ src/bin/main.rs:226
1 INFO  interrupt: half: [1039, 1043, 1042, 1045]: 1040
└─ main::app::dma1_ch1 @ src/bin/main.rs:81
2 INFO  interrupt: half: [1039, 1043, 1042, 1045]: 1042
└─ main::app::dma1_ch1 @ src/bin/main.rs:81
3 INFO  interrupt: half: [1039, 1043, 1042, 1045]: 1041
└─ main::app::dma1_ch1 @ src/bin/main.rs:81
4 INFO  interrupt: half: [1039, 1043, 1042, 1045]: 1042
0

There are 0 answers