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

Feature request: Add support for SPI (optionally PIO) with DMA Transfers #1192

Closed
DatanoiseTV opened this issue Feb 15, 2023 · 12 comments · Fixed by #2168
Closed

Feature request: Add support for SPI (optionally PIO) with DMA Transfers #1192

DatanoiseTV opened this issue Feb 15, 2023 · 12 comments · Fixed by #2168
Labels
enhancement New feature or request

Comments

@DatanoiseTV
Copy link
Contributor

For some applications like controlling a SPI display, DAC or ADC DMA would make sense to save some CPU cycles.
An example is my PicoADK Board, which has an on-board SPI ADC128S102 8ch 1msps ADC.
With DMA, the SPI ADC could be free-running or sampled at a certain interval without involving the CPU.

If this would be done using the HW SPI peripheral, the CS pin would be most likely limited to the CS pins listed
for the corresponding CS options in the pin matrix.

An alternative approach would be to use PIO (there is a reference implementation for SPI). This would still allow
for high clock rates, but more flexibility to the choice of the SPI pins.

@DatanoiseTV DatanoiseTV changed the title Feature Request: Add support for DMA SPI Feature request: Add support for SPI (optionally PIO) with DMA Transfers Feb 15, 2023
@earlephilhower earlephilhower added the enhancement New feature or request label Feb 15, 2023
@DatanoiseTV
Copy link
Contributor Author

@earlephilhower If you want to test it, you can use the PicoADK board I've sent you some time ago.

@earlephilhower
Copy link
Owner

This is pretty low prio for me, to be honest. If it's important for you, I think it's doable without too many tears.

The AudioBufferManager already handles most of DMA work (covers PIO I2S in and out, internal ADC reads, and internal PWM output) so this is more on figuring out how to get SPI repeated xactions and designing an API that makes sense for the common use cases. The RP2040 datasheet and Pico-SDK docs would be the best guides to get you going on the SPI HW config.

@DatanoiseTV
Copy link
Contributor Author

@earlephilhower For the PicoADK it would be pretty important. I've tried my luck without success so far.
Problem is also, that the TX Channel needs to send following data to cycle between adc ch0-ch7 for example:

volatile const uint16_t txbuf[TEST_SIZE] __attribute__((aligned(TEST_SIZE * sizeof(uint16_t)))) = {
                                           0 << 11,
                                           1 << 11,
                                           2 << 11,
                                           3 << 11,
                                           4 << 11,
                                           5 << 11,
                                           6 << 11,
                                           7 << 11
                                           };

@earlephilhower
Copy link
Owner

earlephilhower commented Feb 15, 2023

Spitballing here, but I think you'd set up an ABM INPUT stream from the SPI read FIFO, and then externally set up a recurrent DMA loop off a timer of 1/fs * sizeof(txbuf) that reads txbuff over and over and writes to the SPI write FIFO. You'll get samples from each channel spread over time, not instantaneously, but other than a constant time offset all will come in at fs.

In this case I'd probably make a special-purpose SPI setup since this looks to have rather different requirements than the general purpose one...

@DatanoiseTV
Copy link
Contributor Author

If you would find some time for this, I would be very grateful! I am not sure I understood your last message.

@myklemykle
Copy link
Contributor

+1 for non-blocking SPI communication! This gets more important as the peripheral's max tx rate gets more slow. My current project spends over 20% of its main loop blocked on an SPI transfer of < 20 bytes. The transfer speed itself wouldn't be a problem if I could do something else with those cycles.

@earlephilhower
Copy link
Owner

earlephilhower commented Jun 11, 2023

I tried hacking out some code for this today and ran into a roadblock.

While getting DMA set up to feed/eat the SPI data is straightforward enough, actually knowing when the SPI transaction is completed is not doable via IRQ. There is no IRQ for SPI done, there is no IRQ even for SPI FIFO empty.

So, while it would be possible to make a SPI::transferDMA(.,..) the user app would need to busy poll the SPI device to determine if it's completed or not. There can't be a simple callback on completion, as I imagine you'd expect. You'd have to do something like:

SPI.transferDMA(...);
<do some stuff that doesn't need the SPI send or recv data>
while (SPI.busy()) { /* busy wait */ }
<do some stuff dependent on SPI data being sent/received, like start a new SPI transaction>

Given that, I'm not sure it's so useful. Thoughts?

@earlephilhower earlephilhower added the waiting for feedback Requires response from original poster label Jun 11, 2023
@LinusHeu
Copy link
Contributor

LinusHeu commented Jun 24, 2023

You know way more about this than I do :D, but:
What's SSPTXINTR? "The transmit interrupt is asserted when there are four or fewer valid entries in the transmit FIFO." Is that like a "FIFO half empty" thing?
Probably a stupid idea: SPI is a fixed clock, right? So could you calculate how long it will take to transmit/read X bytes and set a timer based on that as a "SPI DMA done interrupt"?😆

Would DMA SPI be applicable to reading (part of) a file from SD card?
If yes, then I think I have an application where DMA would improve performance but a "spi done" interrupt would not be needed.
void loop(){

  1. Without DMA: read X bytes from SD
    With DMA: Wait for SPI to be done reading X bytes. (Or don't wait if it's already done.) Then request the next X bytes
  2. decoder
  3. volume mixer
  4. send to I2S
    }

As long as steps 2-4 always take longer than the SPI transfer, a "SPI done" interrupt would be useless in this case.
If steps 2-4 are faster than the SPI transfer, then a "SPI done" interrupt would be nicer than busy waiting. But DMA would still increase performance.

@earlephilhower
Copy link
Owner

What's SSPTXINTR? "The transmit interrupt is asserted when there are four or fewer valid entries in the transmit FIFO." Is that like a "FIFO half empty" thing?

Yeah, but not a precise half-empty one. Given the "or fewer" part I'm not sure what the designer expected the code to do.

Probably a stupid idea: SPI is a fixed clock, right? So could you calculate how long it will take to transmit/read X bytes and set a timer based on that as a "SPI DMA done interrupt"?

Actually, that seems like a possibility. No clock stretching allowed like in I2C so you can know timing. I'd need to think about the granularity of alarms, though. Might normally be too small or something...

Would DMA SPI be applicable to reading (part of) a file from SD card? (example)

The SPI is also used to read things like FAT tables and directory entries inside the SDFat code so w/o massive rewriting internally I don't think it could work. Your data reads are a very special case, I think. Inside SDFat there is no differentiation between what purpose the SPI access is being performed.

@DatanoiseTV
Copy link
Contributor Author

Couldn't the SPI transaction / reading be done using the DREQ from the timer? I've seen some examples where SPI transfers were done at 44100Hz rate, but can't find it anymore.

@earlephilhower
Copy link
Owner

It's not quite that simple when talking to SD cards. Even if you didn't have a filesystem and used it like a tape you'd still need to initiate each chunk of sector reads and wait for ready. So you'd need to buffer anyway, and in that case you'd probably just want to run as fast as possible and not limit its speed.

Add a filesystem on top and you could end up needing to read a different address every 512 bytes (plus maybe peeking at the FAT if you don't cache it because it's too big).

@DatanoiseTV
Copy link
Contributor Author

It's not quite that simple when talking to SD cards. Even if you didn't have a filesystem and used it like a tape you'd still need to initiate each chunk of sector reads and wait for ready. So you'd need to buffer anyway, and in that case you'd probably just want to run as fast as possible and not limit its speed.

Add a filesystem on top and you could end up needing to read a different address every 512 bytes (plus maybe peeking at the FAT if you don't cache it because it's too big).

Ah, I see. For an SPI ADC / DAC that would probably make sense, though.

@earlephilhower earlephilhower removed the waiting for feedback Requires response from original poster label Dec 4, 2023
earlephilhower added a commit that referenced this issue May 21, 2024
Fixes #1192

Uses DMA operations to avoid the need to bit-bang or busy wait for SPI
operations that might be very slow. Optional, adds new API calls to enable.
Simple example included.
earlephilhower added a commit that referenced this issue May 21, 2024
Fixes #1192

Uses DMA operations to avoid the need to bit-bang or busy wait for SPI
operations that might be very slow. Optional, adds new API calls to enable.
Simple example included.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants