Created
September 18, 2020 12:55
-
-
Save andresv/716b81d35b206b5476ac61c5efc2d562 to your computer and use it in GitHub Desktop.
stm32g0 adc using dma
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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