diff options
| author | kalkyl <[email protected]> | 2022-12-10 08:26:35 +0100 |
|---|---|---|
| committer | kalkyl <[email protected]> | 2022-12-10 08:26:35 +0100 |
| commit | 1ee58492fbc58b721dc5ed9037c6787af257cbeb (patch) | |
| tree | ce840a88cd6dec6b21005943a547f6418e9a1f1b | |
| parent | 5d4f09156af094732edc5c01332af8b13db10e0f (diff) | |
embassy-rp: Add multicore support
| -rw-r--r-- | embassy-rp/Cargo.toml | 2 | ||||
| -rw-r--r-- | embassy-rp/src/critical_section_impl.rs | 142 | ||||
| -rw-r--r-- | embassy-rp/src/lib.rs | 2 | ||||
| -rw-r--r-- | embassy-rp/src/multicore.rs | 266 | ||||
| -rw-r--r-- | examples/rp/Cargo.toml | 3 | ||||
| -rw-r--r-- | examples/rp/src/bin/multicore.rs | 62 |
6 files changed, 475 insertions, 2 deletions
diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml index 284d458c6..07cd1bc1b 100644 --- a/embassy-rp/Cargo.toml +++ b/embassy-rp/Cargo.toml | |||
| @@ -50,7 +50,7 @@ nb = "1.0.0" | |||
| 50 | cfg-if = "1.0.0" | 50 | cfg-if = "1.0.0" |
| 51 | cortex-m-rt = ">=0.6.15,<0.8" | 51 | cortex-m-rt = ">=0.6.15,<0.8" |
| 52 | cortex-m = "0.7.6" | 52 | cortex-m = "0.7.6" |
| 53 | critical-section = "1.1" | 53 | critical-section = { version = "1.1", features = ["restore-state-u8"] } |
| 54 | futures = { version = "0.3.17", default-features = false, features = ["async-await"] } | 54 | futures = { version = "0.3.17", default-features = false, features = ["async-await"] } |
| 55 | chrono = { version = "0.4", default-features = false, optional = true } | 55 | chrono = { version = "0.4", default-features = false, optional = true } |
| 56 | embedded-io = { version = "0.4.0", features = ["async"], optional = true } | 56 | embedded-io = { version = "0.4.0", features = ["async"], optional = true } |
diff --git a/embassy-rp/src/critical_section_impl.rs b/embassy-rp/src/critical_section_impl.rs new file mode 100644 index 000000000..ce284c856 --- /dev/null +++ b/embassy-rp/src/critical_section_impl.rs | |||
| @@ -0,0 +1,142 @@ | |||
| 1 | use core::sync::atomic::{AtomicU8, Ordering}; | ||
| 2 | |||
| 3 | use crate::pac; | ||
| 4 | |||
| 5 | struct RpSpinlockCs; | ||
| 6 | critical_section::set_impl!(RpSpinlockCs); | ||
| 7 | |||
| 8 | /// Marker value to indicate no-one has the lock. | ||
| 9 | /// | ||
| 10 | /// Initialising `LOCK_OWNER` to 0 means cheaper static initialisation so it's the best choice | ||
| 11 | const LOCK_UNOWNED: u8 = 0; | ||
| 12 | |||
| 13 | /// Indicates which core owns the lock so that we can call critical_section recursively. | ||
| 14 | /// | ||
| 15 | /// 0 = no one has the lock, 1 = core0 has the lock, 2 = core1 has the lock | ||
| 16 | static LOCK_OWNER: AtomicU8 = AtomicU8::new(LOCK_UNOWNED); | ||
| 17 | |||
| 18 | /// Marker value to indicate that we already owned the lock when we started the `critical_section`. | ||
| 19 | /// | ||
| 20 | /// Since we can't take the spinlock when we already have it, we need some other way to keep track of `critical_section` ownership. | ||
| 21 | /// `critical_section` provides a token for communicating between `acquire` and `release` so we use that. | ||
| 22 | /// If we're the outermost call to `critical_section` we use the values 0 and 1 to indicate we should release the spinlock and set the interrupts back to disabled and enabled, respectively. | ||
| 23 | /// The value 2 indicates that we aren't the outermost call, and should not release the spinlock or re-enable interrupts in `release` | ||
| 24 | const LOCK_ALREADY_OWNED: u8 = 2; | ||
| 25 | |||
| 26 | unsafe impl critical_section::Impl for RpSpinlockCs { | ||
| 27 | unsafe fn acquire() -> u8 { | ||
| 28 | RpSpinlockCs::acquire() | ||
| 29 | } | ||
| 30 | |||
| 31 | unsafe fn release(token: u8) { | ||
| 32 | RpSpinlockCs::release(token); | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | impl RpSpinlockCs { | ||
| 37 | unsafe fn acquire() -> u8 { | ||
| 38 | // Store the initial interrupt state and current core id in stack variables | ||
| 39 | let interrupts_active = cortex_m::register::primask::read().is_active(); | ||
| 40 | // We reserved 0 as our `LOCK_UNOWNED` value, so add 1 to core_id so we get 1 for core0, 2 for core1. | ||
| 41 | let core = pac::SIO.cpuid().read() as u8 + 1; | ||
| 42 | // Do we already own the spinlock? | ||
| 43 | if LOCK_OWNER.load(Ordering::Acquire) == core { | ||
| 44 | // We already own the lock, so we must have called acquire within a critical_section. | ||
| 45 | // Return the magic inner-loop value so that we know not to re-enable interrupts in release() | ||
| 46 | LOCK_ALREADY_OWNED | ||
| 47 | } else { | ||
| 48 | // Spin until we get the lock | ||
| 49 | loop { | ||
| 50 | // Need to disable interrupts to ensure that we will not deadlock | ||
| 51 | // if an interrupt enters critical_section::Impl after we acquire the lock | ||
| 52 | cortex_m::interrupt::disable(); | ||
| 53 | // Ensure the compiler doesn't re-order accesses and violate safety here | ||
| 54 | core::sync::atomic::compiler_fence(Ordering::SeqCst); | ||
| 55 | // Read the spinlock reserved for `critical_section` | ||
| 56 | if let Some(lock) = Spinlock31::try_claim() { | ||
| 57 | // We just acquired the lock. | ||
| 58 | // 1. Forget it, so we don't immediately unlock | ||
| 59 | core::mem::forget(lock); | ||
| 60 | // 2. Store which core we are so we can tell if we're called recursively | ||
| 61 | LOCK_OWNER.store(core, Ordering::Relaxed); | ||
| 62 | break; | ||
| 63 | } | ||
| 64 | // We didn't get the lock, enable interrupts if they were enabled before we started | ||
| 65 | if interrupts_active { | ||
| 66 | cortex_m::interrupt::enable(); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | // If we broke out of the loop we have just acquired the lock | ||
| 70 | // As the outermost loop, we want to return the interrupt status to restore later | ||
| 71 | interrupts_active as _ | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | unsafe fn release(token: u8) { | ||
| 76 | // Did we already own the lock at the start of the `critical_section`? | ||
| 77 | if token != LOCK_ALREADY_OWNED { | ||
| 78 | // No, it wasn't owned at the start of this `critical_section`, so this core no longer owns it. | ||
| 79 | // Set `LOCK_OWNER` back to `LOCK_UNOWNED` to ensure the next critical section tries to obtain the spinlock instead | ||
| 80 | LOCK_OWNER.store(LOCK_UNOWNED, Ordering::Relaxed); | ||
| 81 | // Ensure the compiler doesn't re-order accesses and violate safety here | ||
| 82 | core::sync::atomic::compiler_fence(Ordering::SeqCst); | ||
| 83 | // Release the spinlock to allow others to enter critical_section again | ||
| 84 | Spinlock31::release(); | ||
| 85 | // Re-enable interrupts if they were enabled when we first called acquire() | ||
| 86 | // We only do this on the outermost `critical_section` to ensure interrupts stay disabled | ||
| 87 | // for the whole time that we have the lock | ||
| 88 | if token != 0 { | ||
| 89 | cortex_m::interrupt::enable(); | ||
| 90 | } | ||
| 91 | } | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | pub struct Spinlock<const N: usize>(core::marker::PhantomData<()>) | ||
| 96 | where | ||
| 97 | Spinlock<N>: SpinlockValid; | ||
| 98 | |||
| 99 | impl<const N: usize> Spinlock<N> | ||
| 100 | where | ||
| 101 | Spinlock<N>: SpinlockValid, | ||
| 102 | { | ||
| 103 | /// Try to claim the spinlock. Will return `Some(Self)` if the lock is obtained, and `None` if the lock is | ||
| 104 | /// already in use somewhere else. | ||
| 105 | pub fn try_claim() -> Option<Self> { | ||
| 106 | // Safety: We're only reading from this register | ||
| 107 | unsafe { | ||
| 108 | let lock = pac::SIO.spinlock(N).read(); | ||
| 109 | if lock > 0 { | ||
| 110 | Some(Self(core::marker::PhantomData)) | ||
| 111 | } else { | ||
| 112 | None | ||
| 113 | } | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | /// Clear a locked spin-lock. | ||
| 118 | /// | ||
| 119 | /// # Safety | ||
| 120 | /// | ||
| 121 | /// Only call this function if you hold the spin-lock. | ||
| 122 | pub unsafe fn release() { | ||
| 123 | unsafe { | ||
| 124 | // Write (any value): release the lock | ||
| 125 | pac::SIO.spinlock(N).write_value(1); | ||
| 126 | } | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | impl<const N: usize> Drop for Spinlock<N> | ||
| 131 | where | ||
| 132 | Spinlock<N>: SpinlockValid, | ||
| 133 | { | ||
| 134 | fn drop(&mut self) { | ||
| 135 | // This is safe because we own the object, and hence hold the lock. | ||
| 136 | unsafe { Self::release() } | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | pub(crate) type Spinlock31 = Spinlock<31>; | ||
| 141 | pub trait SpinlockValid {} | ||
| 142 | impl SpinlockValid for Spinlock<31> {} | ||
diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index d21b5f7b0..9a2fb7fc3 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | // This mod MUST go first, so that the others see its macros. | 5 | // This mod MUST go first, so that the others see its macros. |
| 6 | pub(crate) mod fmt; | 6 | pub(crate) mod fmt; |
| 7 | 7 | ||
| 8 | mod critical_section_impl; | ||
| 8 | mod intrinsics; | 9 | mod intrinsics; |
| 9 | 10 | ||
| 10 | pub mod adc; | 11 | pub mod adc; |
| @@ -23,6 +24,7 @@ pub mod usb; | |||
| 23 | 24 | ||
| 24 | pub mod clocks; | 25 | pub mod clocks; |
| 25 | pub mod flash; | 26 | pub mod flash; |
| 27 | pub mod multicore; | ||
| 26 | mod reset; | 28 | mod reset; |
| 27 | 29 | ||
| 28 | // Reexports | 30 | // Reexports |
diff --git a/embassy-rp/src/multicore.rs b/embassy-rp/src/multicore.rs new file mode 100644 index 000000000..dd3b70a67 --- /dev/null +++ b/embassy-rp/src/multicore.rs | |||
| @@ -0,0 +1,266 @@ | |||
| 1 | //! Multicore support | ||
| 2 | //! | ||
| 3 | //! This module handles setup of the 2nd cpu core on the rp2040, which we refer to as core1. | ||
| 4 | //! It provides functionality for setting up the stack, and starting core1. | ||
| 5 | //! | ||
| 6 | //! The entrypoint for core1 can be any function that never returns, including closures. | ||
| 7 | |||
| 8 | use core::mem::ManuallyDrop; | ||
| 9 | use core::sync::atomic::{compiler_fence, Ordering}; | ||
| 10 | |||
| 11 | use crate::pac; | ||
| 12 | |||
| 13 | /// Errors for multicore operations. | ||
| 14 | #[derive(Debug)] | ||
| 15 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 16 | pub enum Error { | ||
| 17 | /// Operation is invalid on this core. | ||
| 18 | InvalidCore, | ||
| 19 | /// Core was unresponsive to commands. | ||
| 20 | Unresponsive, | ||
| 21 | } | ||
| 22 | |||
| 23 | /// Core ID | ||
| 24 | #[derive(Debug)] | ||
| 25 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 26 | pub enum CoreId { | ||
| 27 | Core0, | ||
| 28 | Core1, | ||
| 29 | } | ||
| 30 | |||
| 31 | #[inline(always)] | ||
| 32 | fn install_stack_guard(stack_bottom: *mut usize) { | ||
| 33 | let core = unsafe { cortex_m::Peripherals::steal() }; | ||
| 34 | |||
| 35 | // Trap if MPU is already configured | ||
| 36 | if core.MPU.ctrl.read() != 0 { | ||
| 37 | cortex_m::asm::udf(); | ||
| 38 | } | ||
| 39 | |||
| 40 | // The minimum we can protect is 32 bytes on a 32 byte boundary, so round up which will | ||
| 41 | // just shorten the valid stack range a tad. | ||
| 42 | let addr = (stack_bottom as u32 + 31) & !31; | ||
| 43 | // Mask is 1 bit per 32 bytes of the 256 byte range... clear the bit for the segment we want | ||
| 44 | let subregion_select = 0xff ^ (1 << ((addr >> 5) & 7)); | ||
| 45 | unsafe { | ||
| 46 | core.MPU.ctrl.write(5); // enable mpu with background default map | ||
| 47 | core.MPU.rbar.write((addr & !0xff) | 0x8); | ||
| 48 | core.MPU.rasr.write( | ||
| 49 | 1 // enable region | ||
| 50 | | (0x7 << 1) // size 2^(7 + 1) = 256 | ||
| 51 | | (subregion_select << 8) | ||
| 52 | | 0x10000000, // XN = disable instruction fetch; no other bits means no permissions | ||
| 53 | ); | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | #[inline(always)] | ||
| 58 | fn core1_setup(stack_bottom: *mut usize) { | ||
| 59 | install_stack_guard(stack_bottom); | ||
| 60 | // TODO: irq priorities | ||
| 61 | } | ||
| 62 | |||
| 63 | /// Multicore execution management. | ||
| 64 | pub struct Multicore { | ||
| 65 | cores: (Core, Core), | ||
| 66 | } | ||
| 67 | |||
| 68 | /// Data type for a properly aligned stack of N 32-bit (usize) words | ||
| 69 | #[repr(C, align(32))] | ||
| 70 | pub struct Stack<const SIZE: usize> { | ||
| 71 | /// Memory to be used for the stack | ||
| 72 | pub mem: [usize; SIZE], | ||
| 73 | } | ||
| 74 | |||
| 75 | impl<const SIZE: usize> Stack<SIZE> { | ||
| 76 | /// Construct a stack of length SIZE, initialized to 0 | ||
| 77 | pub const fn new() -> Stack<SIZE> { | ||
| 78 | Stack { mem: [0; SIZE] } | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | impl Multicore { | ||
| 83 | /// Create a new |Multicore| instance. | ||
| 84 | pub fn new() -> Self { | ||
| 85 | Self { | ||
| 86 | cores: (Core { id: CoreId::Core0 }, Core { id: CoreId::Core1 }), | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | /// Get the available |Core| instances. | ||
| 91 | pub fn cores(&mut self) -> &mut (Core, Core) { | ||
| 92 | &mut self.cores | ||
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 96 | /// A handle for controlling a logical core. | ||
| 97 | pub struct Core { | ||
| 98 | pub id: CoreId, | ||
| 99 | } | ||
| 100 | |||
| 101 | impl Core { | ||
| 102 | /// Spawn a function on this core. | ||
| 103 | pub fn spawn<F>(&mut self, stack: &'static mut [usize], entry: F) -> Result<(), Error> | ||
| 104 | where | ||
| 105 | F: FnOnce() -> bad::Never + Send + 'static, | ||
| 106 | { | ||
| 107 | fn fifo_write(value: u32) { | ||
| 108 | unsafe { | ||
| 109 | let sio = pac::SIO; | ||
| 110 | // Wait for the FIFO to have some space | ||
| 111 | while !sio.fifo().st().read().rdy() { | ||
| 112 | cortex_m::asm::nop(); | ||
| 113 | } | ||
| 114 | // Signal that it's safe for core 0 to get rid of the original value now. | ||
| 115 | sio.fifo().wr().write_value(value); | ||
| 116 | } | ||
| 117 | |||
| 118 | // Fire off an event to the other core. | ||
| 119 | // This is required as the other core may be `wfe` (waiting for event) | ||
| 120 | cortex_m::asm::sev(); | ||
| 121 | } | ||
| 122 | |||
| 123 | fn fifo_read() -> u32 { | ||
| 124 | unsafe { | ||
| 125 | let sio = pac::SIO; | ||
| 126 | // Keep trying until FIFO has data | ||
| 127 | loop { | ||
| 128 | if sio.fifo().st().read().vld() { | ||
| 129 | return sio.fifo().rd().read(); | ||
| 130 | } else { | ||
| 131 | // We expect the sending core to `sev` on write. | ||
| 132 | cortex_m::asm::wfe(); | ||
| 133 | } | ||
| 134 | } | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | fn fifo_drain() { | ||
| 139 | unsafe { | ||
| 140 | let sio = pac::SIO; | ||
| 141 | while sio.fifo().st().read().vld() { | ||
| 142 | let _ = sio.fifo().rd().read(); | ||
| 143 | } | ||
| 144 | } | ||
| 145 | } | ||
| 146 | |||
| 147 | match self.id { | ||
| 148 | CoreId::Core1 => { | ||
| 149 | // The first two ignored `u64` parameters are there to take up all of the registers, | ||
| 150 | // which means that the rest of the arguments are taken from the stack, | ||
| 151 | // where we're able to put them from core 0. | ||
| 152 | extern "C" fn core1_startup<F: FnOnce() -> bad::Never>( | ||
| 153 | _: u64, | ||
| 154 | _: u64, | ||
| 155 | entry: &mut ManuallyDrop<F>, | ||
| 156 | stack_bottom: *mut usize, | ||
| 157 | ) -> ! { | ||
| 158 | core1_setup(stack_bottom); | ||
| 159 | let entry = unsafe { ManuallyDrop::take(entry) }; | ||
| 160 | // Signal that it's safe for core 0 to get rid of the original value now. | ||
| 161 | fifo_write(1); | ||
| 162 | entry() | ||
| 163 | } | ||
| 164 | |||
| 165 | // Reset the core | ||
| 166 | unsafe { | ||
| 167 | let psm = pac::PSM; | ||
| 168 | psm.frce_off().modify(|w| w.set_proc1(true)); | ||
| 169 | while !psm.frce_off().read().proc1() { | ||
| 170 | cortex_m::asm::nop(); | ||
| 171 | } | ||
| 172 | psm.frce_off().modify(|w| w.set_proc1(false)); | ||
| 173 | } | ||
| 174 | |||
| 175 | // Set up the stack | ||
| 176 | let mut stack_ptr = unsafe { stack.as_mut_ptr().add(stack.len()) }; | ||
| 177 | |||
| 178 | // We don't want to drop this, since it's getting moved to the other core. | ||
| 179 | let mut entry = ManuallyDrop::new(entry); | ||
| 180 | |||
| 181 | // Push the arguments to `core1_startup` onto the stack. | ||
| 182 | unsafe { | ||
| 183 | // Push `stack_bottom`. | ||
| 184 | stack_ptr = stack_ptr.sub(1); | ||
| 185 | stack_ptr.cast::<*mut usize>().write(stack.as_mut_ptr()); | ||
| 186 | |||
| 187 | // Push `entry`. | ||
| 188 | stack_ptr = stack_ptr.sub(1); | ||
| 189 | stack_ptr.cast::<&mut ManuallyDrop<F>>().write(&mut entry); | ||
| 190 | } | ||
| 191 | |||
| 192 | // Make sure the compiler does not reorder the stack writes after to after the | ||
| 193 | // below FIFO writes, which would result in them not being seen by the second | ||
| 194 | // core. | ||
| 195 | // | ||
| 196 | // From the compiler perspective, this doesn't guarantee that the second core | ||
| 197 | // actually sees those writes. However, we know that the RP2040 doesn't have | ||
| 198 | // memory caches, and writes happen in-order. | ||
| 199 | compiler_fence(Ordering::Release); | ||
| 200 | |||
| 201 | let p = unsafe { cortex_m::Peripherals::steal() }; | ||
| 202 | let vector_table = p.SCB.vtor.read(); | ||
| 203 | |||
| 204 | // After reset, core 1 is waiting to receive commands over FIFO. | ||
| 205 | // This is the sequence to have it jump to some code. | ||
| 206 | let cmd_seq = [ | ||
| 207 | 0, | ||
| 208 | 0, | ||
| 209 | 1, | ||
| 210 | vector_table as usize, | ||
| 211 | stack_ptr as usize, | ||
| 212 | core1_startup::<F> as usize, | ||
| 213 | ]; | ||
| 214 | |||
| 215 | let mut seq = 0; | ||
| 216 | let mut fails = 0; | ||
| 217 | loop { | ||
| 218 | let cmd = cmd_seq[seq] as u32; | ||
| 219 | if cmd == 0 { | ||
| 220 | fifo_drain(); | ||
| 221 | cortex_m::asm::sev(); | ||
| 222 | } | ||
| 223 | fifo_write(cmd); | ||
| 224 | |||
| 225 | let response = fifo_read(); | ||
| 226 | if cmd == response { | ||
| 227 | seq += 1; | ||
| 228 | } else { | ||
| 229 | seq = 0; | ||
| 230 | fails += 1; | ||
| 231 | if fails > 16 { | ||
| 232 | // The second core isn't responding, and isn't going to take the entrypoint, | ||
| 233 | // so we have to drop it ourselves. | ||
| 234 | drop(ManuallyDrop::into_inner(entry)); | ||
| 235 | return Err(Error::Unresponsive); | ||
| 236 | } | ||
| 237 | } | ||
| 238 | if seq >= cmd_seq.len() { | ||
| 239 | break; | ||
| 240 | } | ||
| 241 | } | ||
| 242 | |||
| 243 | // Wait until the other core has copied `entry` before returning. | ||
| 244 | fifo_read(); | ||
| 245 | |||
| 246 | Ok(()) | ||
| 247 | } | ||
| 248 | _ => Err(Error::InvalidCore), | ||
| 249 | } | ||
| 250 | } | ||
| 251 | } | ||
| 252 | |||
| 253 | // https://github.com/nvzqz/bad-rs/blob/master/src/never.rs | ||
| 254 | mod bad { | ||
| 255 | pub(crate) type Never = <F as HasOutput>::Output; | ||
| 256 | |||
| 257 | pub trait HasOutput { | ||
| 258 | type Output; | ||
| 259 | } | ||
| 260 | |||
| 261 | impl<O> HasOutput for fn() -> O { | ||
| 262 | type Output = O; | ||
| 263 | } | ||
| 264 | |||
| 265 | type F = fn() -> !; | ||
| 266 | } | ||
diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index 60a8ba94d..bd624a329 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml | |||
| @@ -18,7 +18,8 @@ embassy-usb-logger = { version = "0.1.0", path = "../../embassy-usb-logger" } | |||
| 18 | defmt = "0.3" | 18 | defmt = "0.3" |
| 19 | defmt-rtt = "0.4" | 19 | defmt-rtt = "0.4" |
| 20 | 20 | ||
| 21 | cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } | 21 | #cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } |
| 22 | cortex-m = { version = "0.7.6" } | ||
| 22 | cortex-m-rt = "0.7.0" | 23 | cortex-m-rt = "0.7.0" |
| 23 | panic-probe = { version = "0.3", features = ["print-defmt"] } | 24 | panic-probe = { version = "0.3", features = ["print-defmt"] } |
| 24 | futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } | 25 | futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } |
diff --git a/examples/rp/src/bin/multicore.rs b/examples/rp/src/bin/multicore.rs new file mode 100644 index 000000000..46d3cd17c --- /dev/null +++ b/examples/rp/src/bin/multicore.rs | |||
| @@ -0,0 +1,62 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use defmt::*; | ||
| 6 | use embassy_executor::Executor; | ||
| 7 | use embassy_executor::_export::StaticCell; | ||
| 8 | use embassy_rp::gpio::{Level, Output}; | ||
| 9 | use embassy_rp::peripherals::PIN_25; | ||
| 10 | use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; | ||
| 11 | use embassy_sync::channel::Channel; | ||
| 12 | use embassy_time::{Duration, Timer}; | ||
| 13 | use embassy_rp::multicore::{Multicore, Stack}; | ||
| 14 | use {defmt_rtt as _, panic_probe as _}; | ||
| 15 | |||
| 16 | static mut CORE1_STACK: Stack<4096> = Stack::new(); | ||
| 17 | static EXECUTOR0: StaticCell<Executor> = StaticCell::new(); | ||
| 18 | static EXECUTOR1: StaticCell<Executor> = StaticCell::new(); | ||
| 19 | static CHANNEL: Channel<CriticalSectionRawMutex, LedState, 1> = Channel::new(); | ||
| 20 | |||
| 21 | enum LedState { | ||
| 22 | On, | ||
| 23 | Off, | ||
| 24 | } | ||
| 25 | |||
| 26 | #[cortex_m_rt::entry] | ||
| 27 | fn main() -> ! { | ||
| 28 | let p = embassy_rp::init(Default::default()); | ||
| 29 | let led = Output::new(p.PIN_25, Level::Low); | ||
| 30 | |||
| 31 | let mut mc = Multicore::new(); | ||
| 32 | let (_, core1) = mc.cores(); | ||
| 33 | let _ = core1.spawn(unsafe { &mut CORE1_STACK.mem }, move || { | ||
| 34 | let executor1 = EXECUTOR1.init(Executor::new()); | ||
| 35 | executor1.run(|spawner| unwrap!(spawner.spawn(core1_task(led)))); | ||
| 36 | }); | ||
| 37 | |||
| 38 | let executor0 = EXECUTOR0.init(Executor::new()); | ||
| 39 | executor0.run(|spawner| unwrap!(spawner.spawn(core0_task()))); | ||
| 40 | } | ||
| 41 | |||
| 42 | #[embassy_executor::task] | ||
| 43 | async fn core0_task() { | ||
| 44 | info!("Hello from core 0"); | ||
| 45 | loop { | ||
| 46 | CHANNEL.send(LedState::On).await; | ||
| 47 | Timer::after(Duration::from_millis(100)).await; | ||
| 48 | CHANNEL.send(LedState::Off).await; | ||
| 49 | Timer::after(Duration::from_millis(400)).await; | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | #[embassy_executor::task] | ||
| 54 | async fn core1_task(mut led: Output<'static, PIN_25>) { | ||
| 55 | info!("Hello from core 1"); | ||
| 56 | loop { | ||
| 57 | match CHANNEL.recv().await { | ||
| 58 | LedState::On => led.set_high(), | ||
| 59 | LedState::Off => led.set_low(), | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } \ No newline at end of file | ||
