Module stm32f4xx_hal::adc

source ·
Expand description

Analog to digital converter configuration.

Status

Most options relating to regular conversions are implemented. One-shot and sequences of conversions have been tested and work as expected.

GPIO to channel mapping should be correct for all supported F4 devices. The mappings were taken from CubeMX. The mappings are feature gated per 4xx device but there are actually sub variants for some devices and some pins may be missing on some variants. The implementation has been split up and commented to show which pins are available on certain device variants but currently the library doesn’t enforce this. To fully support the right pins would require 10+ more features for the various variants.

Todo

  • Injected conversions
  • Analog watchdog config
  • Discontinuous mode

Examples

One-shot conversion

use stm32f4xx_hal::{
  gpio::gpioa,
  adc::{
    Adc,
    config::{AdcConfig, SampleTime},
  },
};

let mut adc = Adc::adc1(device.ADC1, true, AdcConfig::default());
let pa3 = gpioa.pa3.into_analog();
let sample = adc.convert(&pa3, SampleTime::Cycles_480);
let millivolts = adc.sample_to_millivolts(sample);
info!("pa3: {}mV", millivolts);

Sequence conversion

use stm32f4xx_hal::{
  gpio::gpioa,
  adc::{
    Adc,
    config::{AdcConfig, SampleTime, Sequence, Eoc, Scan, Clock},
  },
};

let config = AdcConfig::default()
    //We'll either need DMA or an interrupt per conversion to convert
    //multiple values in a sequence
    .end_of_conversion_interrupt(Eoc::Conversion)
    //Scan mode is also required to convert a sequence
    .scan(Scan::Enabled)
    //And since we're looking for one interrupt per conversion the
    //clock will need to be fairly slow to avoid overruns breaking
    //the sequence. If you are running in debug mode and logging in
    //the interrupt, good luck... try setting pclk2 really low.
    //(Better yet use DMA)
    .clock(Clock::Pclk2_div_8);
let mut adc = Adc::adc1(device.ADC1, true, config);
let pa0 = gpioa.pa0.into_analog();
let pa3 = gpioa.pa3.into_analog();
adc.configure_channel(&pa0, Sequence::One, SampleTime::Cycles_112);
adc.configure_channel(&pa3, Sequence::Two, SampleTime::Cycles_480);
adc.configure_channel(&pa0, Sequence::Three, SampleTime::Cycles_112);
adc.start_conversion();

External trigger

A common mistake on STM forums is enabling continuous mode but that causes it to start capturing on the first trigger and capture as fast as possible forever, regardless of future triggers. Continuous mode is disabled by default but I thought it was worth highlighting.

Getting the timer config right to make sure it’s sending the event the ADC is listening to can be a bit of a pain but the key fields are highlighted below. Try hooking a timer channel up to an external pin with an LED or oscilloscope attached to check it’s really generating pulses if the ADC doesn’t seem to be triggering.

use stm32f4xx_hal::{
  gpio::gpioa,
  adc::{
    Adc,
    config::{AdcConfig, SampleTime, Sequence, Eoc, Scan, Clock},
  },
};

 let config = AdcConfig::default()
     //Set the trigger you want
     .external_trigger(TriggerMode::RisingEdge, ExternalTrigger::Tim_1_cc_1);
 let mut adc = Adc::adc1(device.ADC1, true, config);
 let pa0 = gpioa.pa0.into_analog();
 adc.configure_channel(&pa0, Sequence::One, SampleTime::Cycles_112);
 //Make sure it's enabled but don't start the conversion
 adc.enable();

//Configure the timer
let mut tim = Timer::tim1(device.TIM1, 1.hz(), clocks);
unsafe {
    let tim = &(*TIM1::ptr());

    //Channel 1
    //Disable the channel before configuring it
    tim.ccer.modify(|_, w| w.cc1e().clear_bit());

    tim.ccmr1_output().modify(|_, w| w
      //Preload enable for channel
      .oc1pe().set_bit()

      //Set mode for channel, the default mode is "frozen" which won't work
      .oc1m().pwm_mode1()
    );

    //Set the duty cycle, 0 won't work in pwm mode but might be ok in
    //toggle mode or match mode
    let max_duty = tim.arr.read().arr().bits() as u16;
    tim.ccr1.modify(|_, w| w.ccr().bits(max_duty / 2));

    //Enable the channel
    tim.ccer.modify(|_, w| w.cc1e().set_bit());

    //Enable the TIM main Output
    tim.bdtr.modify(|_, w| w.moe().set_bit());
}

Modules

  • Contains types related to ADC configuration

Structs

  • Analog to Digital Converter
  • Core temperature internal signal
  • Vbat internal signal, used for monitoring the battery (if used)
  • Vref internal signal, used for calibration