Last active
January 26, 2021 20:35
-
-
Save albertmoravec/989129440d92d1a95ef458c7ee84638b to your computer and use it in GitHub Desktop.
ADC DMA multiple channels
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
#![no_std] | |
#![no_main] | |
#[macro_use] | |
mod utilities; | |
use stm32f4xx_hal as hal; | |
use cortex_m::singleton; | |
use hal::{ | |
adc::config::{ | |
AdcConfig, Dma, ExternalTrigger, Resolution, SampleTime, Scan, Sequence, TriggerMode, | |
}, | |
adc::Adc, | |
delay::Delay, | |
dma::config::DmaConfig, | |
dma::traits::{DMASet, PeriAddress}, | |
dma::{Channel0, PeripheralToMemory, Stream0, StreamsTuple, Transfer}, | |
gpio::gpioa::{PA0, PA1}, | |
gpio::Analog, | |
pac::{ADC1, DMA2, TIM1}, | |
prelude::*, | |
rcc::Clocks, | |
stm32, | |
timer::Timer, | |
}; | |
use log::info; | |
use rtic::app; | |
type Buffer = &'static mut [u16; 2]; | |
type DmaTransfer = Transfer<Stream0<DMA2>, Channel0, ADC1DMA, PeripheralToMemory, Buffer>; | |
pub struct ADC1DMA { | |
address: u32, | |
} | |
impl ADC1DMA { | |
fn new(address: u32) -> ADC1DMA { | |
ADC1DMA { address } | |
} | |
} | |
unsafe impl PeriAddress for ADC1DMA { | |
type MemSize = u16; | |
fn address(&self) -> u32 { | |
self.address | |
} | |
} | |
unsafe impl DMASet<Stream0<DMA2>, Channel0, PeripheralToMemory> for ADC1DMA {} | |
#[app(device = stm32f4xx_hal::stm32, peripherals = true)] | |
const APP: () = { | |
struct Resources { | |
adc: Adc<ADC1>, | |
timer: Timer<TIM1>, | |
transfer: DmaTransfer, | |
empty_buffer: Option<Buffer>, | |
delay: Delay, | |
} | |
#[init] | |
fn init(cx: init::Context) -> init::LateResources { | |
utilities::logger::init(); | |
let core: cortex_m::Peripherals = cx.core; | |
let device: stm32::Peripherals = cx.device; | |
let rcc = device.RCC; | |
rcc.ahb1enr.modify(|_, w| w.gpioaen().enabled()); | |
let clocks = rcc | |
.constrain() | |
.cfgr | |
.use_hse(25.mhz()) | |
.sysclk(100.mhz()) | |
.pclk1(50.mhz()) | |
.pclk2(100.mhz()) | |
.freeze(); | |
let gpioa = device.GPIOA.split(); | |
let pa0 = gpioa.pa0.into_analog(); | |
let pa1 = gpioa.pa1.into_analog(); | |
let first_buffer = singleton!(: [u16; 2] = [0; 2]).unwrap(); | |
let second_buffer = singleton!(: [u16; 2] = [0; 2]).unwrap(); | |
let mut adc = init_adc(device.ADC1, pa0, pa1); | |
let timer = init_tim(device.TIM1, clocks); | |
let transfer = init_dma( | |
device.DMA2, | |
ADC1DMA::new(adc.data_register_address()), | |
first_buffer, | |
); | |
init::LateResources { | |
adc, | |
timer, | |
transfer, | |
empty_buffer: Some(second_buffer), | |
delay: Delay::new(core.SYST, clocks), | |
} | |
} | |
#[idle(resources = [delay, transfer, empty_buffer])] | |
fn idle(cx: idle::Context) -> ! { | |
let delay = cx.resources.delay; | |
let mut transfer = cx.resources.transfer; | |
let mut empty_buffer = cx.resources.empty_buffer; | |
transfer.lock(|tr| tr.start(|_| {})); | |
loop { | |
let (mut val1, mut val2) = (0, 0); | |
empty_buffer.lock(|buf| { | |
let vals = buf.as_ref().unwrap(); | |
val1 = vals[0]; | |
val2 = vals[1]; | |
}); | |
info!("Value 1: {}", val1); | |
info!("Value 2: {}", val2); | |
delay.delay_ms(1000u32); | |
} | |
} | |
#[task(binds = DMA2_STREAM0, priority = 2, resources = [transfer, empty_buffer])] | |
fn dma(cx: dma::Context) { | |
let new = cx.resources.empty_buffer.take().unwrap(); | |
let old = cx | |
.resources | |
.transfer | |
.next_transfer(new) | |
.map_err(|_| {}) | |
.unwrap() | |
.0; | |
*cx.resources.empty_buffer = Some(old); | |
} | |
}; | |
fn init_dma(dma: DMA2, adc: ADC1DMA, buffer: Buffer) -> DmaTransfer { | |
let stream_0 = StreamsTuple::new(dma).0; | |
let config = DmaConfig::default() | |
.transfer_complete_interrupt(true) | |
.memory_increment(true); | |
DmaTransfer::init(stream_0, adc, buffer, None, config) | |
} | |
fn init_adc(adc: ADC1, pa0: PA0<Analog>, pa1: PA1<Analog>) -> Adc<ADC1> { | |
let config = AdcConfig::default() | |
.dma(Dma::Continuous) | |
.external_trigger(TriggerMode::RisingEdge, ExternalTrigger::Tim_1_cc_1) | |
.scan(Scan::Enabled) | |
.resolution(Resolution::Ten); | |
let mut adc = Adc::adc1(adc, true, config); | |
adc.configure_channel(&pa0, Sequence::One, SampleTime::Cycles_480); | |
adc.configure_channel(&pa1, Sequence::Two, SampleTime::Cycles_480); | |
adc | |
} | |
fn init_tim(tim: TIM1, clocks: Clocks) -> Timer<TIM1> { | |
let timer = Timer::tim1(tim, 10.hz(), clocks); | |
// This part is taken from the ADC HAL documentation | |
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()); | |
} | |
timer | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment