Skip to content

Instantly share code, notes, and snippets.

@andresv
Created September 18, 2020 12:55
Show Gist options
  • Save andresv/716b81d35b206b5476ac61c5efc2d562 to your computer and use it in GitHub Desktop.
Save andresv/716b81d35b206b5476ac61c5efc2d562 to your computer and use it in GitHub Desktop.
stm32g0 adc using dma
use cortex_m::asm;
use hal::stm32;
use hal::prelude::*;
use hal::analog::adc::{Precision, SampleTime};
use hal::timer::Timer;
use hal::time::Hertz;
pub type AdcBuf = [u16; 4];
pub fn init(adc_dma_buf: &AdcBuf) {
// notice that G0 HAL does not have ADC DMA support yet, therefore it is done here manually
let rcc = unsafe { &(*stm32::RCC::ptr()) };
let adc = unsafe { &(*stm32::ADC::ptr()) };
let dma = unsafe { &(*stm32::DMA::ptr()) };
let dmamux = unsafe { &(*stm32::DMAMUX::ptr()) };
// set ADC clock source to System clock
rcc.ccipr.modify(|_, w| unsafe { w.adcsel().bits(0b00) });
// enable ADC clock
rcc.apbenr2.modify(|_, w| w.adcen().set_bit());
// async ADC clock can be sourced from SYSCLK, HSI16 and PLLP
// it can be prescaled using ADC_CCR PRESC[3:0] from 1 to 256
// however async clock introduces jitter if ADC conversions are triggered using
// external trigger like timer
// therefore here ADC clock is derived from APB PCLK/4
// notice that for this synchronous source ADC_CCR PRESC[3:0] cannot be used as prescaler
// if SYSCLOCK is 64 MHz and APB Prescaler is 1 then PCLK is also 64 MHz
// and ADC clock is PCLK/4 = 64/4 = 16 MHz
adc.cfgr2.write(|w| unsafe { w.ckmode().bits(0b10) });
// enable ADC voltage regulator
adc.cr.modify(|_, w| w.advregen().set_bit());
for _ in 0 .. 1_000 { asm::nop() }
// perform calibration
adc.cr.modify(|_, w| w.adcal().set_bit());
while adc.isr.read().eocal().bit_is_clear() {}
// configure ADC settings
unsafe {
adc.cfgr1.write(|w|
w
.res().bits(Precision::B_12 as u8)
// discontinuous mode, software or hardware event is needed to start sequence conversion
.discen().clear_bit()
// right aligned
.align().clear_bit()
// DMA circular mode
.dmacfg().set_bit()
// generate DMA requests
.dmaen().set_bit()
// select TIM6_TRGO as hardware trigger
.extsel().bits(0b101)
// enable hardware trigger on rising edge
.exten().bits(0b01)
);
// set sampling time to 40
// tconv = Sampling time + 12.5 x ADC clock cycles
// with 16 MHz clock it is:
// tconv = (40 + 12.5) * ADC clock cycles = 92.5 * 0.063 = 3.281 us per channel
adc.smpr.modify(|_, w| w.smp1().bits(SampleTime::T_40 as u8));
}
// select ADC channel IN3, IN4, IN5, IN6
adc.chselr().write(|w| unsafe { w.chsel().bits(0x78) });
// reset DMA
rcc.ahbrstr.modify(|_, w| w.dmarst().set_bit());
rcc.ahbrstr.modify(|_, w| w.dmarst().clear_bit());
// enable DMA clock
rcc.ahbenr.modify(|_,w| w.dmaen().set_bit());
// setup DMA channel 1 to read out ADC data
unsafe {
dma.ccr1.write( |w|
w
// priority level 2
.pl().bits(0b10)
// memory/peripheral request size: 16-bit (1)
.msize().bits(0b01)
.psize().bits(0b01)
// cirular mode
.circ().set_bit()
// increment memory ptr, do not increment periph ptr
.minc().set_bit()
.pinc().clear_bit()
// disable 'memory-to-memory' mode
.mem2mem().clear_bit()
// use 'peripheral -> memory' transfer direction
.dir().clear_bit()
// enable only transfer complete interrupt
.teie().clear_bit()
.htie().clear_bit()
.tcie().set_bit()
);
// route DMA channel 1 to ADC - on STM32G0 it is done using DMAMUX
// notice that DMAMUX is 0-indexed, however DMA channel is 1 indexed
// check dmareq_id from reference manual: "table 42. DMAMUX: assignment of multiplexer inputs to resources"
// so 5 means use ADC as DMA input source
dmamux.dmamux_c0cr.write( |w| w.dmareq_id().bits(5).ege().set_bit());
// setup DMA to transfer data from ADC_DR register to adc_dma_buf
let adc_data_register_addr = &adc.dr as *const _ as u32;
let adc_dma_buf_addr : u32 = adc_dma_buf as *const AdcBuf as u32;
dma.cndtr1.write( |w| w.ndt().bits(adc_dma_buf.len() as u16));
dma.cpar1.write( |w| w.pa().bits(adc_data_register_addr) );
dma.cmar1.write( |w| w.ma().bits(adc_dma_buf_addr));
}
}
pub fn start(dma: &mut stm32::DMA, adc: &mut stm32::ADC, mut trigger: Timer<hal::stm32::TIM6>, samplerate: Hertz) {
// enable DMA
dma.ccr1.modify(|_r,w| w.en().set_bit());
// enable ADC
adc.isr.modify(|_, w| w.adrdy().set_bit());
adc.cr.modify(|_, w| w.aden().set_bit());
while adc.isr.read().adrdy().bit_is_clear() {}
// ADC conversions are started using TIM6 TRGO event
let tim6 = unsafe { &(*stm32::TIM6::ptr()) };
// update mode - the update event is selected as a trigger output (TRGO)
tim6.cr2.modify(|_, w| unsafe { w.mms().bits(0b010) });
trigger.start(samplerate);
// start listening TRGO events
adc.cr.modify(|_, w| w.adstart().set_bit());
}
#[allow(dead_code)]
pub fn raw_to_mv(raw: u16) -> u16 {
let vref: u32 = 3300;
(raw as u32 * vref / 4095) as u16
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment