stm32-rs / stm32f3xx-hal

A Rust embedded-hal HAL for all MCUs in the STM32 F3 family
https://crates.io/crates/stm32f3xx-hal
Apache License 2.0
163 stars 67 forks source link

Using Serial DMA for variable sized transfers #325

Open robamu opened 1 year ago

robamu commented 1 year ago

Hello,

I think this question might also be suited in a forum post. I am using a STM32F3-Disco in my current project so I thought that I can also ask this here.

I am currently planning on how to perform variably sized transfers using DMA + IRQ + RTIC. According to some forum posts, this is definitely possible using the IDLE line or the Receiver Timeout IRQ by the USART peripheral. More specifically, I wanted to have one RTIC task which uses DMA to send all TX data, and one RTIC task to receive variable sized RX packets, which are only limited in their maximum size.

Unfortunately, I have not really found an example for this in Rust yet and I only have experience with variable sized transfers using a combination of FIFO + IRQ on a smaller Cortex-M0 based system. Most of the DMA based API I have found uses the read_exact API which uses a fixed buffer size.

I've looked around and experimented myself a little bit. I have found this code snippet: https://github.com/kalkyl/f303-rtic/blob/main/src/bin/serial.rs , which goes into the direction of what I require.

Some thoughts I had:

  1. TX: is usually a little bit easier. I have a RTIC task which reads from a heapless queue or some other form of queue and sends any contained frames with DMA. My first approach would be to simply wait until the transfer is complete because I'd like to unblock the shared serial as soon as possible. The second approach instead uses a transfer complete IRQ, but then I am not fully sure how to access the USART peripheral without trickery or hacks just to read IRQ events, for example for pending RX
  2. RX: I configure a receive timeout IRQ and then initiate a DMA transfer with the maximum expected packet length. When receiving a recv timeout IRQ or a transfer complete, I stop the DMA transfer (for recv timeout) and restart a new one with the maximum expected packet length. I pass the read packet into a heapless queue or some other form of queue which is processed by another task.

Now, from what I have see from the STM32F3 HAL docs, this will not be straightforward. One problem is that I am forced to split the serial struct for DMA transfers, as the transfer struct takes ownership of the RX or TX part. This definitely makes sense, but I need the serial struct to check IRQ events . I could retrieve those using the join method, but that will never be possible because a DMA transfer for RX will practically be active permanently, and thus the RX part will always be unavailable, being cached in some transfer struct unavailable to the TX task. What is the common approach when facing a problem like that? One idea I had was to adapt the split function to return the USART peripheral so I can use it at my own peril for things like this.

Maybe someone has a better idea how to do this? Maybe there is also some other aspect I am forgetting here.. I checked the available interrupts of the STM32F303 again. I guess the core problem is that the interrupt register is used both for RX and TX, although some of the events can be checked independently for RX and TX..

robamu commented 1 year ago

Some ideas

  1. Maybe add a method called split_returning_periph which returns the USART in addition to the TX and RX parts.
  2. I simply use pac::Peripeherals::steal().USART? in my code
  3. A bit fancier: I think I am only interested in the IRQ status register for determining stop conditions for RX DMA. My idea would be to have some helper structs like RxIrqStatus and TxIrqStatus which can be retrieved from the TX and RX transfer handles respectively. These registers are also read-only. These structs can then tell me what TX or RX specific events occured. That way, I don't have hack around the HAL like when using way 1 or way 2. The transfer structs still own the TX and RX handles so I still need to think how to solve this..
robamu commented 1 year ago

I was actually able to implement variable sized transfers in my RTIC application now and it seems to work well :-) All I needed was some more access to some IRQ status registers on RX, TX, and their DMA wrappers.

I am thinking of preparing an minimal example where variable sized strings can be sent to the device and will be echoed back (or even processed, for example to switch on LEDs for certain cmd strings). I think this might be useful to other people, especially because it shows how to use DMA in combination with heapless, RTIC and message passing to set up an efficient and flexible UART Interface. If you are interested, I could prepare this after all necessary PRs were merged.