earlephilhower / arduino-pico

Raspberry Pi Pico Arduino core, for all RP2040 boards
GNU Lesser General Public License v2.1
1.92k stars 402 forks source link

The function rp2040.fifo.available() does not work as advertised. #2100

Closed frohro closed 4 months ago

frohro commented 4 months ago

The docs say this function tells you the number of uint32_t left in the buffer. It seems to give the number you have used instead. Also, it is ambiguous which core it is giving you the number for, because "this core's fifo" does not tell you if the core that writes the FIFO or the core that reads the FIFO owns it. :-). I am assuming it is a way you can see if the opposite core has read the data you gave it. Here is my messy test code showing this behavior.

// Demonstrates a simple use of the setup1()/loop1() functions
// for a multiprocessor run.

// The normal, core0 setup
void setup() {
  Serial.begin(115200);
  delay(5000);
  Serial.print("Max number of fifo positions available for core0 is: ");
  uint32_t available = rp2040.fifo.available();
  Serial.println(available);
  rp2040.fifo.push(1);
  rp2040.fifo.push(2);
  Serial.print("Number of fifo positions for core0 available is: ");
  Serial.println(rp2040.fifo.available());
}

void loop() {
}

// Running on core1
void setup1() {
  delay(5000);
  Serial.print("Max number of fifo positions available for core1 is: ");
  Serial.println(rp2040.fifo.available());
  rp2040.fifo.push(3);
  rp2040.fifo.push(4);
  rp2040.fifo.push(5);
  Serial.print("Number of fifo positions for core1 available is: ");
  Serial.println(rp2040.fifo.available());
  Serial.print("Reading the first fifo from core0: ");
  uint32_t first = rp2040.fifo.pop();
  Serial.println(first);
  uint32_t second = rp2040.fifo.pop();
  Serial.print("Reading the second fifo element form core 0: ");
  Serial.println(second);
}

void loop1() {
}

Thanks for all your good work on the Arduino-Pico tools.

earlephilhower commented 4 months ago

I think there's a misunderstanding here. The available method works like a Stream.available() or Serial.available() and returns the number of words that can be read, not the free space. So starting w/rp2040.available() == 0 is definitely the expected case. Just like in the Serial case, the idea is that you can see if there's data there before doing a possibly blocking read.

frohro commented 4 months ago

Thanks @earlephilhower , For newbies like me, it might help if the brief docs were made a tad more descriptive. For example, what you wrote above would be a nice addition to the docs. Here are some other things that I wonder about. 1) Is it right that there is a FIFO that Core 0 sends messages to Core 1, and another FIFO that Core 1 sends messages to Core 0? It seems that way from my testing and even seems implied, but not explicit in the docs. 2) What is the number of uint32_t that can be used, or what limits it? 3) I am assuming that I can pass pointers back and forth between cores, which can each access the memory pointed to, as long as the other core does not try to at the same time. I hope I'm right about this. 4) An example of 3 above would be nice.

earlephilhower commented 4 months ago
  1. Yes, there's one fifo from each core to the other. It's replacing the HW ones onboard which need to be used to interrupt the other core for things like flash writes
  2. 8 entries, like the HW ones
  3. Yes you can, this is a shared memory multicore system

There's no reason you can't implement your own FIFO of any size and type, if desired. And for multiprocessor comms there are many other things you can use, like shared volatile globals, single write/single reader queues, etc. instead of the FIFO. I implemented it this way to replicate the HW FIFO which needs to be used by the core and SDK to let one core interrupt the other core (there's no other way than the HW FIFO to send an IRQ from Core0->Core1/Core1->Core0, strangely enough!).

frohro commented 4 months ago

@earlephilhower , I'll work on a pull request for the docs on the multicore communication. Thanks for the answers!

frohro commented 4 months ago

I couldn't figure out the pull-request procedure. Here is the multicore.rst I modified though. You would need to read it. I am not 100% sure about it, but it reflects my experience.


Multicore Processing
====================

The RP2040 chip has 2 cores that can run independently of each other, sharing
peripherals and memory with each other.  Arduino code will normally execute
only on core 0, with the 2nd core sitting idle in a low power state.

By adding a ``setup1()`` and ``loop1()`` function to your sketch you can make
use of the second core.  Anything called from within the ``setup1()`` or
``loop1()`` routines will execute on the second core.

``setup()`` and ``setup1()`` will be called at the same time, and the ``loop()``
or ``loop1()`` will be started as soon as the core's ``setup()`` completes (i.e.
not necessarily simultaneously!).

See the ``Multicore.ino`` example in the ``rp2040`` example directory for a
quick introduction.

Stack Sizes
-----------

When the Pico is running in single core mode, core 0 has the full 8KB of stack
space available to it.  When using multicore ``setup1``/``loop1`` the 8KB is split
into two 4K stacks, one per core.  It is possible for core 0's stack to overwrite
core 1's stack in this case, if you go beyond the 4K limitation.

To allocate a separate 8K stack for core 1, resulting in 8K stacks being available
for both cores, simply define the following variable in your sketch and set it
to ``true``:

.. code:: cpp

    bool core1_separate_stack = true;

Pausing Cores
-------------

Sometimes an application needs to pause the other core on chip (i.e. it is
writing to flash or needs to stop processing while some other event occurs).

void rp2040.idleOtherCore()
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Sends a message to stop the other core (i.e. when called from core 0 it
pauses core 1, and vice versa).  Waits for the other core to acknowledge
before returning.

The other core will have its interrupts disabled and be busy-waiting in
an RAM-based routine, so flash and other peripherals can be accessed.

**NOTE** If you idle core 0 too long, then the USB port can become frozen.
This is because core 0 manages the USB and needs to service IRQs in a
timely manner (which it can't do when idled).  In most cases, you should
try not to idle a core if you can get by using a mutex.  You can use the
mutex functions from available in the Raspberry Pi Pico SDK to protect 
shared memory.

void rp2040.resumeOtherCore()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Resumes processing in the other core, where it left off.

void rp2040.restartCore1()
~~~~~~~~~~~~~~~~~~~~~~~~~~

Hard resets Core1 from Core 0 and restarts its operation from ``setup1()``.

Communicating Between Cores
---------------------------

The RP2040 provides a hardware FIFO for communicating between cores, but it
is used exclusively for the idle/resume calls described above.  Instead, please
use the following functions to access a software-managed, multicore safe
FIFO. There is a separate FIFO for each core.  One core writes to it, the other
reads from it. 

It is important to understand that you can share memory between the two
cores, but you must ensure no core writes while the other is reading or writing.
Both cores can read from shared memory at the same time, but only one core can
write at a time.  You can use mutexes or other synchronization primitives to
ensure this. 

void rp2040.fifo.push(uint32_t)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Pushes a value to the other core.  Will block if the FIFO is full.

bool rp2040.fifo.push_nb(uint32_t)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Pushes a value to the other core.  If the FIFO is full, returns ``false``
immediately and doesn't block.  If the push is successful, returns ``true``.

uint32_t rp2040.fifo.pop()
~~~~~~~~~~~~~~~~~~~~~~~~~~

Reads a value from this core's FIFO.  Blocks until one is available.

bool rp2040.fifo.pop_nb(uint32_t \*dest)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Reads a value from this core's FIFO and places it in dest.  Will return
``true`` if successful, or ``false`` if the pop would block.

int rp2040.fifo.available()
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Returns the number of values available for this core to read from its FIFO.
'''
earlephilhower commented 4 months ago

2114 has some of your suggestions. I don't really want to put too much specificity in the doc because multicore operations are a complex task with lots of gotchas. A doc file in the core is not the place to explain those kinds of fundamentals because they're just too subtle...