Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: DMAMUX support #12

Closed
andresv opened this issue May 24, 2020 · 7 comments
Closed

Feature: DMAMUX support #12

andresv opened this issue May 24, 2020 · 7 comments
Labels
enhancement New feature or request

Comments

@andresv
Copy link
Member

andresv commented May 24, 2020

G0 is different from other STM32 MCUs because this uses a thing called DMAMUX. Well actually H7, L4+ and WB series are also using it but currently it is not implemented in their Rust HALs. So we can be the first ones to tackle this problem.

If this is done it allows to port a lot of DMA based drivers from stm32l0xx-hal which are relatively mature.

27.05.2020: Recently rust embedded wg suggested that this is way to go with DMA: https://github.com/ra-kete/dma-poc.
And an example how it is used: stm32-rs/stm32f3xx-hal#86

@andresv andresv added the enhancement New feature or request label May 24, 2020
@andresv andresv changed the title Feature: traits for DMAMUX Feature: DMAMUX support May 24, 2020
@ijager
Copy link
Contributor

ijager commented Jul 1, 2020

Do you have some design in mind already? I have a project now where I need uart+dma, so maybe I can help a bit.

@andresv
Copy link
Member Author

andresv commented Jul 1, 2020

I do not have any particular design in mind yet and I would be very happy with PRs about this feature.
Basically as far as I know this is what Rust embedded WG proposes for next gen DMA design: https://github.com/ra-kete/dma-poc.
Some more information: https://github.com/rust-embedded/wg/blob/master/projects/in-progress/0440-dma-api-documentation.md.

@ijager
Copy link
Contributor

ijager commented Jul 9, 2020

Do you by any chance have Rust code using DMAMUX without using HAL? Just the PAC. Otherwise I think that's something I will start with.

I found this application note AN5224.

@andresv
Copy link
Member Author

andresv commented Jul 9, 2020

Yes, I have written specialized ADC driver with hardcoded channels that is using DMAMUX.

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 fn init(adc_dma_buf: &[u16; 6]) {
    // 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 channels
    adc.chselr().write(|w| unsafe { w.chsel().bits(0x4B9) });

    // 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 [u16; 6] 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());
}

pub fn raw_to_mv(raw: u16) -> u16 {
    let vref: u32 = 3300;
    (raw as u32 * vref / 4096) as u16
}

@ijager
Copy link
Contributor

ijager commented Jul 22, 2020

I managed to make a working UART - DMA transfer with help of your example, see this gist.

So I kinda understand now what DMAMUX is for. However the manual mentions you can also do peripheral-to-peripheral DMA transfers. But it is not clear to me how you can configure that. Since the DMAMUX CxCR register only requires 1 DMAREQ_ID table 36.

@andresv
Copy link
Member Author

andresv commented Jul 22, 2020

I think it is ok if peripheral-to-peripheral transfers are not supported in first version. If we could to peripheral-to-memory and memory-to-peripheral then pretty much everything is covered (UART, ADC, SPI, I2C, etc).

@andresv
Copy link
Member Author

andresv commented Aug 11, 2020

Dmamux support is now merged.

@andresv andresv closed this as completed Aug 11, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants