1

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
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
tdot123
  • 11
  • 1
  • It looks like in init_dma, adc_buffer is a local variable, and you're setting the CMARx register to the address of that. Once init_dma returns, that address is no longer valid (all bets are off w.r.t. memory safety when you use unsafe...) – lkolbly Mar 01 '22 at 21:47
  • when you are using CubeMX then you need both "Continuous Conversion Mode" and "DMA Continuous Request" to be enabled. If "DMA Continuous Requests" is off, the ADC will keep sampling, but the DMA transfer is stopped after the first sample sequence – Chris_B Mar 02 '22 at 06:51

0 Answers0