I'm building a firmware to capture very short burst signals through the ADC on a STM32F401CC (Black Pill board).
I have a 100 Hz PWM output running on a different GPIO group
The ADC readout gets offset for about 1 ms every time the PWM timer channel fires. Since this is extremely hard to debug, I've added a signal graph directly on device - here's an example:
There's 17ms of ADC data between the two Xs, and the voltage range is around 0.3-2V. The distance between the notches changes as I change the PWM timer frequency.
Here's a shorter signal, "zoomed in":
Things I've already tried:
- A different power supply
- Disabling any non strictly necessary interrupt handlers
- Lowering ADC sample rate
- Sampling ADC for more cycles
- Changing ADC clock
- Scaling input level
- Errata workarounds for ADC precision
The input is a pulled-down SFH309FA phototransistor on the A0 pin.
I'm using Rust/RTIC with the following ADC configuration:
let adc_pin = gpioa.pa0.into_analog(); let adc_config = AdcConfig::default() .dma(Dma::Continuous) .scan(Scan::Disabled) .clock(Clock::Pclk2_div_6) .resolution(Resolution::Twelve); let mut adc = Adc::adc1(dp.ADC1, true, adc_config); adc.configure_channel(&adc_pin, Sequence::One, SampleTime::Cycles_3); DMA Configuration:
let dma = StreamsTuple::new(dp.DMA2); let dma_config = DmaConfig::default() .transfer_complete_interrupt(true) .double_buffer(false); let transfer = Transfer::init_peripheral_to_memory( dma.0, adc, cx.local.first_buffer, None, dma_config, ); Timer:
let mut timer = dp.TIM2.counter_hz(&clocks); timer.listen(Event::Update); timer.start(50_000.Hz()).unwrap(); Timer handler:
#[task(binds = TIM2, shared = [transfer], local = [timer])] fn adcstart(mut cx: adcstart::Context) { cx.shared.transfer.lock(|transfer| { transfer.start(|adc| { adc.start_conversion(); }); }); cx.local.timer.clear_flags(Flag::Update); } DMA completion interrupt handler:
#[task(binds = DMA2_STREAM0, shared = [transfer, adc_value, sample_counter, calibration_state, measurement], local = [adc_dma_buffer], priority = 3)] fn dma(ctx: dma::Context) { let mut shared = ctx.shared; let local = ctx.local; let last_adc_dma_buffer = shared.transfer.lock(|transfer| { let (last_adc_dma_buffer, _) = transfer .next_transfer(local.adc_dma_buffer.take().unwrap()) .unwrap(); last_adc_dma_buffer }); let value = *last_adc_dma_buffer; *local.adc_dma_buffer = Some(last_adc_dma_buffer); //....store value } PWM setup:
let mut pwm = dp .TIM4 .pwm_hz(hal::timer::Channel4::new(gpiob.pb9), 200.Hz(), &clocks); pwm.enable(hal::timer::Channel::C4); pwm.set_duty(hal::timer::Channel::C4, 0); The entire project is available at https://github.com/eugeny/shutterspeed2
I'm just starting out with embedded and would appreciate any pointers.


