japaric / stm32f103xx-hal

HAL for the STM32F103xx family of microcontrollers
Apache License 2.0
116 stars 39 forks source link

DMA CNDTR Serial RX #48

Open MabezDev opened 6 years ago

MabezDev commented 6 years ago

I am using the DMA with a circular buffer to recieve serial data on a UART, its working fine if it fills up both halves of the circ buffer. The problem I am having is how to grab the data still in the buffer when the serial data stops coming in.

In a C project at work I had used the CNDTR register in the DMA channel to figure out where in the buffer and how much data had been read after a timeout. Looking though dma.rs I see there is a function to access the CNDTR reg but its private to the crate. I am wondering if this is something that should be exposed.

I am fairly new to rust so I may be missing something obvious. I'd also like to mention that the work you are doing for embedded rust is amazing!

japaric commented 6 years ago

I was thinking of something like this recently and I think we could have peek expose both the half that's not being modified (or has been completed) and the completed fraction of the other half. Something like this:

// [X, X, X, ?, ?, ?, ?, ?, X, X, X, X, X, X, X, X]
//  ~~~~~~~ in progress     ~~~~~~~~~~~~~~~~~~~~~~ completed
// "?" = to be written by the DMA
circ_buffer.peek(
    |in_progress_half: &[u8], completed_half: &[u8; 16], half: Half| {
//   ~~~~~~~~~~~~~~~~ NEW!
        let n = half_being_modified.len();
        let cndtr = if half == Half::Second {
            32 - n
        } else {
            16 - n
        };
    },
);

There may be some more descriptive names for the halves ...

Thoughts? Perhaps there should be two methods?

MabezDev commented 6 years ago

This looks promising. To keep things simpler, what about just returning each half of the buffer based on the half given as a param? E.g if you pass Half::Second you would get the current state of thye second part of the buffer, which could be either the full amount or a fraction of the buffer.

circ_buffer.peek(
    |requested_half_buf: &[u8], half: Half| {
//   ~~~~~~~~~~~~~~~~ NEW!
        let n = half_requested.len();
        let cndtr = if half == Half::Second {
            32 - n
        } else {
            16 - n
        };
    },
);
thenewwazoo commented 6 years ago

Here is my solution. I call this when my USART line goes idle:

pub fn partial_peek<R, F, T>(&mut self, f: F) -> Result<R, Error>
    where
    F: FnOnce(&[T], Half) -> Result<(usize, R), ()>,
    B: Unsize<[T]>,
{
    // this inverts expectation and returns the half being _written_
    let buf = match self.readable_half {
        Half::First => &self.buffer[1],
        Half::Second => &self.buffer[0],
    };

    //                          ,- half-buffer
    //    [ x x x x y y y y y z | z z z z z z z z z z ]
    //                       ^- pending=11
    let pending = self.channel.get_cndtr() as usize; // available bytes in _whole_ buffer

    let slice: &[T] = buf;
    let capacity = slice.len(); // capacity of _half_ a buffer
    //     <--- capacity=10 --->
    //    [ x x x x y y y y y z | z z z z z z z z z z ]

    let pending = if pending > capacity {
        pending - capacity
    } else {
        pending
    };

    //                          ,- half-buffer
    //    [ x x x x y y y y y z | z z z z z z z z z z ]
    //                       ^- pending=1

    let end = capacity - pending;
    //    [ x x x x y y y y y z | z z z z z z z z z z ]
    //                       ^- end=9
    //             ^- consumed_offset=4
    //             [y y y y y] <-- slice
    let slice = &slice[self.consumed_offset..end];

    match f(slice, self.readable_half) {
        Ok((l, r)) => { self.consumed_offset += l; Ok(r) },
        Err(_) => Err(Error::BufferError),
    }
}

Not included in the listing is the addition of the consumed_offset: usize member of CircBuffer