Quantcast
Viewing all articles
Browse latest Browse all 4852

SDK • Re: RP2040 inter thread synchronisation

then on one core you enable that interrupt (in the peripheral's interrupt enable register) and on the other core you clear the enable in your interrupt handler - using hw_set_bits()/hw_clr_bits()
Thanks. Could you give me specific code or register names? All I can find in the SDK doc is functions which state they only affect the core from which they are called. Maybe there are some errors there, I've already found some.
Hardware-wise, the only things that are specific to the individual cores are the NVIC (which is integral to the core), and parts of the SIO (which has two separate ports to the two cores and appears slightly differently from the two sides). Everything else is connected to the main memory bus and behaves identically whichever core you access it from.

The SIO is rather a special case. It exists to take advantage of the fact that the CortexM0+ cores have the fast i/o port on the side, which can do loads/stores to faster (single cycle) than normal access to memory or memory-mapped I/O (two cycles), but with some limitations as to whact can be attached to it. Many other manufactures' CortexM0+ designs simply put GPIO registers in the fast port space and nothing else. In RP2040 the two cores each have their fast port connected to the "SIO". Inside the SIO, some blocks are duplicated so that each core has its own independent piece of hardware: the divide unit and the interpolators; these can be thought of as being effectively part of the cores themselves since they are totally dedicated and nothing can access them apart from the core that they are attached to. Then there's the features specifically intended for inter-core communication (the FIFO and spinlocks), where there's only one copy but the cores have mirrored connections to it - for example, each core has just one FIFO write port and one FIFO read port, but the FIFOs are crossed over between the two cores. Finally, there's the GPIO access, which is where it gets most complex.

The first thing to say is that "GPIO" can have multiple meanings - sometimes it refers to the physical pins on the device that can be used for multiple functions, sometimes it refers to the ability to twiddle pins under software control as distinct from peripherals like a UART or SPI. In RP2040, the software-pin-twiddling aspect is part of the SIO, everything else (pin multiplexing, drive strengths etc) has a standard set of registers on the main bus like all the other peripherals.

The next thing to notice is the facilities for individual bit set/clear. This is a common requirement; other devices often have ad-hoc set/clear register aliases for the ones you want to use most often, and expect you to disable interrupts round a read-modify-write sequence for everything else. For RP2040 where there's two CPUs and so disabling interrupts isn't enough, comprehensive set/clear/xor aliases have been provided for every peripheral register in the system so that you can safely modify individual bits within registers even if the other core is touching the same register at the same time. For almost all peripherals, this is provided as part of the bus interface in a uniform fashion by adding an offset to the register address, and the SDK provides the wrapper hw_set_bits(register, bits) (and similarly hw_clear_bits(), hw_xor_bits()) to do the address arithmetic for you. Unfortunately, the limitations of the fast port mean that the SIO has to be an exception to this scheme - you can't use hw_set_bits() on SIO registers. Instead, the important ones (such as the GPIO pin values and pin directions) have been given explicit, named, alias registers that allow you to set and clear bits in them.

The GPIO interrupts are NOT part of the SIO - they are part of the pin control settings (documented in datasheet section 2.19 GPIO, rather than 2.3.1 SIO).

Finally, there's a bit of architectural ugliness here. For many of the peripherals (including GPIO), it's useful to have some of the interrupts serviced by one CPU core and some by the other. For everything apart from GPIO (for example, DMA, PIO units), this is done symmetrically - there's two interrupts from the peripheral, two sets of enable registers in the peripheral, and the interrupts are wired in parallel to both cores; hence you might choose to use PIO0_IRQ_0 on core0 and PIO_0_IRQ1 on core1, but you don't have to (and can use both on the same core if it suits). GPIO is a special case - it again has two sets of enables and two interrupts, but they are dedicated to particular cores. So rather than having two separate interrupts in the NVIC for the two peripheral interrupts, there's only one (IO_IRQ_BANK0) and each core is wired to just one of the pair of GPIO interrupts. It doesn't make much practical difference, but it's ugly and inconsistent.


(I'll answer the question you actually asked in a separate post!)

Statistics: Posted by arg001 — Sat Aug 10, 2024 2:08 pm



Viewing all articles
Browse latest Browse all 4852

Trending Articles