Sharing buffers or sending data between interrupt handler and main thread

Hi,

I've browsed through the internet a bit and looked at some options, but there does not seem to be a clear-cut or ideal way to solve this issue (yet). I was hoping to get some more ideas here because I am still new to embedded rust. What is the best way to share data like arrays or buffers in general between the main thread and an interrupt handler?

My goal was to write an I2C Slave interrupt handler. There are the following requirements

  • The slave interrupt handler takes care of loading received data from a FIFO into a receive buffer which can then be used by the main thread
  • The slave interrupt handlers takes care of loading the transmit FIFO from a shared buffer. This shared buffer can be filled by the main thread as well

My initial skeleton code can be found here: va108xx-hal-rs/i2c-slave.rs at mueller/i2c-slave · robamu-org/va108xx-hal-rs · GitHub

My plan is to make the IRQ handler part of the I2cSlave struct because this struct also consumes the I2C peripheral. Also, it's the higher level abstraction I can use to tweak how the IRQ handler operates.

My first though was to have a member function like this:

                pub fn slave_irq_handler(&mut self, buffer: &mut [u8]) {
                    let irq_enb = self.i2c_base.i2c.irq_enb.read();
                }

However, this assumes some buffer is passed to the IRQ handler, and the IRQ handler is now part of the HAL which makes using components like RTIC .. more difficult or impossible?

I though this was also a good use case for channels, but the standard channels provided by rust only are available for std applications if I understand correctly.

I think I will have the same issue when writing something like an interrupt handler for a UART which permanently empties the reception FIFO, ensuring that no data is missed. Doing something like this without interrupts seems to be difficult to impossible to me in any non-trivial application, and being reliant on an UART IRQ handler to make sure all data is read into some data structure is something I have already implemented multiple times for MCUs and is a common use-case for embedded applications in my opinion. Therefore, I think it might also be a good idea to maybe add a chapter or section to the embedded rust book specifying how to solve issues like this.

What would be some methods and ways to solve this issue? Especially if parts of the IRQ code are part of a HAL.

Kind Regards
Robin

However, this assumes some buffer is passed to the IRQ handler, and the IRQ handler is now part of the HAL which makes using components like RTIC .. more difficult or impossible?

I don't understand this statement. Why would that be incompatible with RTIC? The HAL shouldn't define the #[interrupt] handler directly, but it can provide functions to be called from the actual handler.

I though this was also a good use case for channels, but the standard channels provided by rust only are available for std applications if I understand correctly.

I would argue that communication between interrupt handlers is an application-level task and does not fall within the responsibility of the Hardware Abstraction Layer. Certainly you can provide convenience functions, to make life easier for users. But anything beyond that, I would argue, is out of scope for the HAL.

Therefore, I think it might also be a good idea to maybe add a chapter or section to the embedded rust book specifying how to solve issues like this.

This is probably a good idea. Although I think the book is quite out of date. At least that's what I remember concluding once before.

What would be some methods and ways to solve this issue? Especially if parts of the IRQ code are part of a HAL.

What you're trying to do sounds a lot like my spi_future module. I don't know if my approach is idiomatic within the broader embedded Rust ecosystem, but I found it useful.

Note

I believe I ran into a small, subtle problem when using SpiFuture a few weeks ago. I implemented a fix in my local copy of the HAL, but I haven't yet pushed the changes upstream. Unfortunately, I can't remember what the problem was off the top of my head.

Also note that the RTIC example is out of date. I need to update that as well.

There's also DMA. If you can find a way to make DMA easy to use, that might be a big help to users. In my case, I was using SpiFuture until the speed got too high, and I had to switch to DMA.

In atsamd-hal, we have a DMA module, but unfortunately, it looks like the documentation is not built by default anywhere. The code is here if you want to take a look. Right now, it can only handle single-descriptor transfers. But we may support more in the future.

Actually you're right, the user code can probably still use RTIC to assign a buffer to the IRQ handler (I have to look at RTIC again, only did the basics..) without the HAL being affected.

While it is correct that the IRQ handler is application-level, most C/C++ libraries I have seen provide a default IRQ handler which can be used to perform common tasks (like for example emptying the RX FIFO) if the requirements for not too special. The HAL already provides higher level structures to work with the peripherals more easily, so this is the perfect place to add (optional) IRQ handlers in my opinion.

In my special use-case on a Cortex-M0 based device, there actually isn't any DMA. The only way to poll data from something like a UART or I2C slave permanently without putting too much load on the CPU / allowing sensible multi-tasking is to set up an IRQ handler to empty the FIFOs all the time.

The future concept sounds good, I'll look into it. I still have a feeling some channel like structure would still be the best solution for me to send all received data to a main thread and leave the data decoding to the main thread.. I was thinking of maybe combining something like BBBuffer in bbqueue - Rust and combine it with RTIC to pass the producer / move the producer to the IRQ handler..

Kind Regards
Robin

The HAL already provides higher level structures to work with the peripherals more easily, so this is the perfect place to add (optional) IRQ handlers in my opinion.

Yes, I agree. I think I was confused by your terminology. When you say "interrupt handler", I think "the literal function called by the interrupt controller", i.e. the function labeled with #[interrupt]. But I don't think that's what you meant. You're talking about a function that users can call optionally call from the #[interrupt] function. That's what I tried to do with SpiFuture.

I still have a feeling some channel like structure would still be the best solution for me to send all received data to a main thread and leave the data decoding to the main thread.

It sounds like you want something like heapless::spsc or heapless::mpmc. RTIC software tasks can also do message passing. You can probably structure your HAL API to be compatible with something like that.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.