rust-embedded / embedded-dma

Apache License 2.0
35 stars 12 forks source link

intended api for serial dma read and write #9

Open pdgilbert opened 3 years ago

pdgilbert commented 3 years ago

I have been working on some examples using different setup() functions for different stm32*xx_hals and then common application code. Some time ago I put aside the serial dma examples because the different hal approaches were too different. Now these examples are some of the few I have not got working (scoreboard at https://pdgilbert.github.io/eg_stm_hal/), and I think there has been some progress adopting embedded-dma as a common approach.

Unfortunately, there do not seem to be examples in any of the hals that I am able to recognize as representing the new approach. Below is code that works with stm32f3xx_hal and the setup() part works with stm32f1xx_hal. stm32f3xx_hal provides buffer using methods read_exact() and write_all() which are used in the code below. Those methods are not provided by other hals and seem rather messy because the application code has to deal explicitly with the buffers rather than hiding them inside TxDma and RxDma objects. I think I should be using something like ReadDma and WriteDma but I am not sure.

I think my setup functions should be able to return buffered version of Tx and Rx. Previously I had it working that way with stm32f1xx_hal using something like tx1.with_dma(channels.4) and rx1.with_dma(channels.5) but I thought the hal was not using embedded-dma at the time and the .with_dma() method was not available in other hals.

Thanks for any insight. (I'm still a newbie so details are useful.)

#![deny(unsafe_code)]
#![no_main]
#![no_std]

#[cfg(debug_assertions)]
extern crate panic_semihosting;

#[cfg(not(debug_assertions))]
extern crate panic_halt;

use cortex_m::singleton;
use cortex_m_rt::entry;

use cortex_m_semihosting::hprintln;

#[cfg(feature = "stm32f1xx")]  //  eg blue pill stm32f103
use stm32f1xx_hal::{prelude::*,   
                    pac::Peripherals, 
                    serial::{Config, Serial, StopBits, Tx, Rx},
            dma::{dma1, }, 
            device::USART1 }; 

    #[cfg(feature = "stm32f1xx")]
    fn setup() ->  (Tx<USART1>, dma1::C4, Rx<USART1>, dma1::C5)  {

    // should be able to do something like
    //fn setup() ->  (impl WriteDma<USART1>, impl ReadDma<USART1> )  {

       let p = Peripherals::take().unwrap();
       let mut rcc = p.RCC.constrain();  
       let clocks = rcc.cfgr.freeze(&mut p.FLASH.constrain().acr); 
       let mut afio = p.AFIO.constrain(&mut rcc.apb2);
       let mut gpioa = p.GPIOA.split(&mut rcc.apb2);

       let txrx1 = Serial::usart1(
       p.USART1,
       (gpioa.pa9.into_alternate_push_pull(&mut gpioa.crh),     //tx pa9, 
            gpioa.pa10),                        //rx pa10
       &mut afio.mapr,
       Config::default() .baudrate(9600.bps()) .stopbits(StopBits::STOP1),
       clocks,
       &mut rcc.apb2,
       );  //.split();

       let (tx1, rx1)  = txrx1.split();

       let dma1 = p.DMA1.split(&mut rcc.ahb);
       let (tx1_ch, rx1_ch) = (dma1.4, dma1.5);

       // should be able to return just (tx1, rx1)

       (tx1, tx1_ch,   rx1, rx1_ch)
       }

#[cfg(feature = "stm32f3xx")]  //  eg Discovery-stm32f303
use stm32f3xx_hal::{prelude::*, 
                    stm32::Peripherals,
            serial::{Serial, Tx, Rx}, 
            dma::dma1, 
            stm32::USART1 
            };

    #[cfg(feature = "stm32f3xx")]
    fn setup() ->  (Tx<USART1>, dma1::C4, Rx<USART1>, dma1::C5)  {

    // should be able to do something like
    //fn setup() ->  (impl WriteDma<USART1>, impl ReadDma<USART1> )  {

       let p = Peripherals::take().unwrap();
       let mut rcc = p.RCC.constrain();  
       let clocks    = rcc.cfgr.freeze(&mut p.FLASH.constrain().acr);
       let mut gpioa = p.GPIOA.split(&mut rcc.ahb); 

       let txrx1 = Serial::usart1(
           p.USART1,
           (gpioa.pa9.into_af7( &mut gpioa.moder, &mut gpioa.afrh), 
        gpioa.pa10.into_af7(&mut gpioa.moder, &mut gpioa.afrh)),
           9600.bps(),
           clocks,
           &mut rcc.apb2,
           );

       let (tx1, rx1)  = txrx1.split();

       let dma1 = p.DMA1.split(&mut rcc.ahb);
       let (tx1_ch, rx1_ch) = (dma1.ch4, dma1.ch5);

       // should be able to return just (tx1, rx1)

       (tx1, tx1_ch,   rx1, rx1_ch)
       }

    // End of hal/MCU specific setup. Following should be generic code.

#[entry]
fn main() -> ! {

    let (tx1, tx1_ch,   rx1, rx1_ch) = setup();

    hprintln!("test write to console ...").unwrap();

    let buf = singleton!(: [u8; 15] = *b"\r\ncheck console").unwrap();

    *buf = *b"\r\nSlowly type  ";  //NB. 15 characters

    // create recv and send structures that can be modified in loop rather than re-assigned.

    let mut recv = rx1.read_exact(buf, rx1_ch).wait();    //this returns 3-tuple (buf, rx1_ch, rx1)

    let mut send = tx1.write_all(recv.0, tx1_ch).wait();  //this returns 3-tuple (buf, tx1_ch, tx1)

    // Note send (write) is using buf as put into recv (read). The returned buffer in recv and
    //   the argument buffer in send are data. The argument buffer in recv may be a holding spot 
    //   to put return buffer? but it is not part of the program logic. The size of the return
    //   buffer from recv does seem to be determined by the size of the recv argument buffer.
    //   The return buffer from send seems like it should be unnecessary, but it does provide
    //   the buffer needed in the recv argument.

    // Read from console into  buf and echo back to console

    hprintln!("Enter 15 characters in console. Repeat.").unwrap();
    hprintln!("Use ^C in gdb to exit.").unwrap();

    //each pass in loop waits for input of 15 chars typed in console then echos them
    loop { 
       recv = recv.2.read_exact(send.0, recv.1).wait();   
       send = send.2.write_all( recv.0, send.1).wait(); 
       }
}
thalesfragoso commented 3 years ago

I think this is out of scope for this crate, we provide the safety requirements of each trait, but the specifics of each design is left for the downstream implementers.

pdgilbert commented 3 years ago

I was beginning to fear that was the case. But what is the mechanism for encouraging the different stm32*-hal's to use a common api? Is it just accident that they all use read and write for character-by-character serial input and output or is that because the api is dictated in embedded-hal or somewhere else? (And where would be a better place to ask this question, because this is probably not the right place?) Thanks, and sorry I am still suffering from newbie confusion.

thalesfragoso commented 3 years ago

I think it would be hard to come up with a good interface that would work nicely with all the different DMA hardware out there. embedded-hal would be a good place for that interface, or maybe even this crate. However, as of today, I'm unaware of such interface.