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

Unsure how to enable DMA interrupts #852

Open
FayCarsons opened this issue Sep 16, 2024 · 3 comments
Open

Unsure how to enable DMA interrupts #852

FayCarsons opened this issue Sep 16, 2024 · 3 comments

Comments

@FayCarsons
Copy link

FayCarsons commented Sep 16, 2024

I'm working on a library (my first serious embedded project) which needs to write to a PIO FIFO continuously. I'm trying to do this with DMA, using an interrupt to re-enqueue the buffers whenever a write finishes, but I'm unsure how to enable an interrupt for a double-buffered DMA transfer in the way I would with, say, a, ADC or PWM. The documentation is a bit confusing and from what I understand some features it mentions are maybe not actually implemented?

I'm initializing DMA like this:

let transfer = unsafe {
    double_buffer::Config::new((dma.ch0, dma.ch1), &mut BITSTREAM, tx)
        .start()
        .read_next(&mut TX_BUF)
};

unsafe {
    TRANSFER_HANDLER = Some(transfer);
    BUFFERS = Some((bitstream, tx_buf));
    rp_pico::pac::NVIC::unmask(rp_pico::pac::Interrupt::DMA_IRQ_0);
}

/* unrelated code ... */

#[hal::pac::interrupt]
unsafe fn DMA_IRQ_0() {
  if let Some((transfer, bufs)) = TRANSFER_HANDLER.take().zip(BUFFERS.take()) {
      loop {
          if transfer.is_done() {
              let (tx_buf, next) = transfer.wait();
              let transfer = next.read_next(tx_buf);
              let bufs = (bufs.1, bufs.0);
              TRANSFER_HANDLER = Some(transfer);
              BUFFERS = Some(bufs);
              break;
          }
      }
  }
}

With the buffers (BITSTREAM and TX_BUF) being [u32; N] and 'tx' being the PIO FIFO.

Do you think this should work as is? Is there something I'm missing? Here's the repo for context: https://github.com/FayCarsons/cosmic-unicorn-rs, the file in question is src/cosmic_unicorn.rs

Any help at all would be deeply appreciated!!!!!

@jannic
Copy link
Member

jannic commented Sep 17, 2024

I see two issues with your code:

  • You busy loop within the interrupt. That doesn't look useful. Either busy loop (then you don't need an interrupt), or quickly do the needed work and return from the interrupt
  • The DMA interrupts are never enabled. rp_pico::pac::NVIC::unmask(rp_pico::pac::Interrupt::DMA_IRQ_0) only unmasks them in the NVIC, but that doesn't help if the DMA peripheral doesn't generate interrupts in the first place. Something like dma.ch0.enable_irq0(); dma.ch1.enable_irq0(); could help.

@FayCarsons
Copy link
Author

@jannic thank you so much for taking a look at this!

So, if the interrupt is triggered the transfer is done? The busy loop was an attempt at being sure of that. In general, it's confusing what's necessary to re-enqueue the buffers once the transfer is complete, the doc comments of is_done and wait lead me to believe either they aren't actually necessary inside the interrupt, or the interrupt being triggered does not necessarily mean the transfer is complete.

Do both channels need interrupts enabled? With the way the configuration works, my assumption was that the transfer would be the thing to enable an interrupt for, not the channels. Should I maybe be using the lower-level DMA API here? It feels like this higher-level API is maybe not what I want

@jannic
Copy link
Member

jannic commented Sep 17, 2024

Sorry, I don't have a complete example available, and don't know the DMA interrupts well enough to quickly write one.
As a rough sketch, I'd try something like this (pseudocode):

fn interrupt_handler() {
  if check_irq0() && transfer.is_done() {
     [your processing here]
  }
}

check_irq0() checks if the dma interrupt is pending, and if so, clears it.
Then, transfer.is_done() is checked. If the transfer is not yet done (which would mean that the interrupt was triggered by something else - should not happen, but who knows?) the handler just returns. Note that there is no loop involved: If the DMA finishes after check_irq0() was called, the interrupt is triggered again and the handler will be called once more.

And yes, both channels need interrupts enabled, because they will be used alternately by the double buffering transfer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants