diff options
| author | Peter Krull <[email protected]> | 2024-09-23 19:02:59 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-09-23 19:02:59 +0200 |
| commit | a2c473306f4a7c8e99add2546450ab3a7a97436e (patch) | |
| tree | 5522a708e492db7d4632dc0a56fe5057244f03f0 /examples/rp/src | |
| parent | e02a987bafd4f0fcf9d80e7c4f6e1504b8b02cec (diff) | |
| parent | 2935290a6222536d6341103f91bfd732165d3862 (diff) | |
Merge branch 'embassy-rs:main' into multi-signal
Diffstat (limited to 'examples/rp/src')
42 files changed, 2561 insertions, 187 deletions
diff --git a/examples/rp/src/bin/adc_dma.rs b/examples/rp/src/bin/adc_dma.rs new file mode 100644 index 000000000..f755cf5bf --- /dev/null +++ b/examples/rp/src/bin/adc_dma.rs | |||
| @@ -0,0 +1,54 @@ | |||
| 1 | //! This example shows how to use the RP2040 ADC with DMA, both single- and multichannel reads. | ||
| 2 | //! For multichannel, the samples are interleaved in the buffer: | ||
| 3 | //! `[ch1, ch2, ch3, ch4, ch1, ch2, ch3, ch4, ...]` | ||
| 4 | #![no_std] | ||
| 5 | #![no_main] | ||
| 6 | |||
| 7 | use defmt::*; | ||
| 8 | use embassy_executor::Spawner; | ||
| 9 | use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler}; | ||
| 10 | use embassy_rp::bind_interrupts; | ||
| 11 | use embassy_rp::gpio::Pull; | ||
| 12 | use embassy_time::{Duration, Ticker}; | ||
| 13 | use {defmt_rtt as _, panic_probe as _}; | ||
| 14 | |||
| 15 | bind_interrupts!(struct Irqs { | ||
| 16 | ADC_IRQ_FIFO => InterruptHandler; | ||
| 17 | }); | ||
| 18 | |||
| 19 | #[embassy_executor::main] | ||
| 20 | async fn main(_spawner: Spawner) { | ||
| 21 | let p = embassy_rp::init(Default::default()); | ||
| 22 | info!("Here we go!"); | ||
| 23 | |||
| 24 | let mut adc = Adc::new(p.ADC, Irqs, Config::default()); | ||
| 25 | let mut dma = p.DMA_CH0; | ||
| 26 | let mut pin = Channel::new_pin(p.PIN_26, Pull::Up); | ||
| 27 | let mut pins = [ | ||
| 28 | Channel::new_pin(p.PIN_27, Pull::Down), | ||
| 29 | Channel::new_pin(p.PIN_28, Pull::None), | ||
| 30 | Channel::new_pin(p.PIN_29, Pull::Up), | ||
| 31 | Channel::new_temp_sensor(p.ADC_TEMP_SENSOR), | ||
| 32 | ]; | ||
| 33 | |||
| 34 | const BLOCK_SIZE: usize = 100; | ||
| 35 | const NUM_CHANNELS: usize = 4; | ||
| 36 | let mut ticker = Ticker::every(Duration::from_secs(1)); | ||
| 37 | loop { | ||
| 38 | // Read 100 samples from a single channel | ||
| 39 | let mut buf = [0_u16; BLOCK_SIZE]; | ||
| 40 | let div = 479; // 100kHz sample rate (48Mhz / 100kHz - 1) | ||
| 41 | adc.read_many(&mut pin, &mut buf, div, &mut dma).await.unwrap(); | ||
| 42 | info!("single: {:?} ...etc", buf[..8]); | ||
| 43 | |||
| 44 | // Read 100 samples from 4 channels interleaved | ||
| 45 | let mut buf = [0_u16; { BLOCK_SIZE * NUM_CHANNELS }]; | ||
| 46 | let div = 119; // 100kHz sample rate (48Mhz / 100kHz * 4ch - 1) | ||
| 47 | adc.read_many_multichannel(&mut pins, &mut buf, div, &mut dma) | ||
| 48 | .await | ||
| 49 | .unwrap(); | ||
| 50 | info!("multi: {:?} ...etc", buf[..NUM_CHANNELS * 2]); | ||
| 51 | |||
| 52 | ticker.next().await; | ||
| 53 | } | ||
| 54 | } | ||
diff --git a/examples/rp/src/bin/assign_resources.rs b/examples/rp/src/bin/assign_resources.rs new file mode 100644 index 000000000..ff6eff4a2 --- /dev/null +++ b/examples/rp/src/bin/assign_resources.rs | |||
| @@ -0,0 +1,79 @@ | |||
| 1 | //! This example demonstrates how to assign resources to multiple tasks by splitting up the peripherals. | ||
| 2 | //! It is not about sharing the same resources between tasks, see sharing.rs for that or head to https://embassy.dev/book/#_sharing_peripherals_between_tasks) | ||
| 3 | //! Of course splitting up resources and sharing resources can be combined, yet this example is only about splitting up resources. | ||
| 4 | //! | ||
| 5 | //! There are basically two ways we demonstrate here: | ||
| 6 | //! 1) Assigning resources to a task by passing parts of the peripherals | ||
| 7 | //! 2) Assigning resources to a task by passing a struct with the split up peripherals, using the assign-resources macro | ||
| 8 | //! | ||
| 9 | //! using four LEDs on Pins 10, 11, 20 and 21 | ||
| 10 | |||
| 11 | #![no_std] | ||
| 12 | #![no_main] | ||
| 13 | |||
| 14 | use assign_resources::assign_resources; | ||
| 15 | use defmt::*; | ||
| 16 | use embassy_executor::Spawner; | ||
| 17 | use embassy_rp::gpio::{Level, Output}; | ||
| 18 | use embassy_rp::peripherals::{self, PIN_20, PIN_21}; | ||
| 19 | use embassy_time::Timer; | ||
| 20 | use {defmt_rtt as _, panic_probe as _}; | ||
| 21 | |||
| 22 | #[embassy_executor::main] | ||
| 23 | async fn main(spawner: Spawner) { | ||
| 24 | // initialize the peripherals | ||
| 25 | let p = embassy_rp::init(Default::default()); | ||
| 26 | |||
| 27 | // 1) Assigning a resource to a task by passing parts of the peripherals. | ||
| 28 | spawner | ||
| 29 | .spawn(double_blinky_manually_assigned(spawner, p.PIN_20, p.PIN_21)) | ||
| 30 | .unwrap(); | ||
| 31 | |||
| 32 | // 2) Using the assign-resources macro to assign resources to a task. | ||
| 33 | // we perform the split, see further below for the definition of the resources struct | ||
| 34 | let r = split_resources!(p); | ||
| 35 | // and then we can use them | ||
| 36 | spawner.spawn(double_blinky_macro_assigned(spawner, r.leds)).unwrap(); | ||
| 37 | } | ||
| 38 | |||
| 39 | // 1) Assigning a resource to a task by passing parts of the peripherals. | ||
| 40 | #[embassy_executor::task] | ||
| 41 | async fn double_blinky_manually_assigned(_spawner: Spawner, pin_20: PIN_20, pin_21: PIN_21) { | ||
| 42 | let mut led_20 = Output::new(pin_20, Level::Low); | ||
| 43 | let mut led_21 = Output::new(pin_21, Level::High); | ||
| 44 | |||
| 45 | loop { | ||
| 46 | info!("toggling leds"); | ||
| 47 | led_20.toggle(); | ||
| 48 | led_21.toggle(); | ||
| 49 | Timer::after_secs(1).await; | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | // 2) Using the assign-resources macro to assign resources to a task. | ||
| 54 | // first we define the resources we want to assign to the task using the assign_resources! macro | ||
| 55 | // basically this will split up the peripherals struct into smaller structs, that we define here | ||
| 56 | // naming is up to you, make sure your future self understands what you did here | ||
| 57 | assign_resources! { | ||
| 58 | leds: Leds{ | ||
| 59 | led_10: PIN_10, | ||
| 60 | led_11: PIN_11, | ||
| 61 | } | ||
| 62 | // add more resources to more structs if needed, for example defining one struct for each task | ||
| 63 | } | ||
| 64 | // this could be done in another file and imported here, but for the sake of simplicity we do it here | ||
| 65 | // see https://github.com/adamgreig/assign-resources for more information | ||
| 66 | |||
| 67 | // 2) Using the split resources in a task | ||
| 68 | #[embassy_executor::task] | ||
| 69 | async fn double_blinky_macro_assigned(_spawner: Spawner, r: Leds) { | ||
| 70 | let mut led_10 = Output::new(r.led_10, Level::Low); | ||
| 71 | let mut led_11 = Output::new(r.led_11, Level::High); | ||
| 72 | |||
| 73 | loop { | ||
| 74 | info!("toggling leds"); | ||
| 75 | led_10.toggle(); | ||
| 76 | led_11.toggle(); | ||
| 77 | Timer::after_secs(1).await; | ||
| 78 | } | ||
| 79 | } | ||
diff --git a/examples/rp/src/bin/bluetooth.rs b/examples/rp/src/bin/bluetooth.rs new file mode 100644 index 000000000..7524e7929 --- /dev/null +++ b/examples/rp/src/bin/bluetooth.rs | |||
| @@ -0,0 +1,150 @@ | |||
| 1 | //! This example test the RP Pico W on board LED. | ||
| 2 | //! | ||
| 3 | //! It does not work with the RP Pico board. See blinky.rs. | ||
| 4 | |||
| 5 | #![no_std] | ||
| 6 | #![no_main] | ||
| 7 | |||
| 8 | use bt_hci::controller::ExternalController; | ||
| 9 | use cyw43_pio::PioSpi; | ||
| 10 | use defmt::*; | ||
| 11 | use embassy_executor::Spawner; | ||
| 12 | use embassy_futures::join::join3; | ||
| 13 | use embassy_rp::bind_interrupts; | ||
| 14 | use embassy_rp::gpio::{Level, Output}; | ||
| 15 | use embassy_rp::peripherals::{DMA_CH0, PIO0}; | ||
| 16 | use embassy_rp::pio::{InterruptHandler, Pio}; | ||
| 17 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 18 | use embassy_time::{Duration, Timer}; | ||
| 19 | use static_cell::StaticCell; | ||
| 20 | use trouble_host::advertise::{AdStructure, Advertisement, BR_EDR_NOT_SUPPORTED, LE_GENERAL_DISCOVERABLE}; | ||
| 21 | use trouble_host::attribute::{AttributeTable, CharacteristicProp, Service, Uuid}; | ||
| 22 | use trouble_host::gatt::GattEvent; | ||
| 23 | use trouble_host::{Address, BleHost, BleHostResources, PacketQos}; | ||
| 24 | use {defmt_rtt as _, embassy_time as _, panic_probe as _}; | ||
| 25 | |||
| 26 | bind_interrupts!(struct Irqs { | ||
| 27 | PIO0_IRQ_0 => InterruptHandler<PIO0>; | ||
| 28 | }); | ||
| 29 | |||
| 30 | #[embassy_executor::task] | ||
| 31 | async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { | ||
| 32 | runner.run().await | ||
| 33 | } | ||
| 34 | |||
| 35 | #[embassy_executor::main] | ||
| 36 | async fn main(spawner: Spawner) { | ||
| 37 | let p = embassy_rp::init(Default::default()); | ||
| 38 | let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); | ||
| 39 | let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); | ||
| 40 | let btfw = include_bytes!("../../../../cyw43-firmware/43439A0_btfw.bin"); | ||
| 41 | |||
| 42 | // To make flashing faster for development, you may want to flash the firmwares independently | ||
| 43 | // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: | ||
| 44 | // probe-rs download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 | ||
| 45 | // probe-rs download 43439A0_clm.bin --format bin --chip RP2040 --base-address 0x10140000 | ||
| 46 | // probe-rs download 43439A0_btfw.bin --format bin --chip RP2040 --base-address 0x10141400 | ||
| 47 | //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 224190) }; | ||
| 48 | //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; | ||
| 49 | //let btfw = unsafe { core::slice::from_raw_parts(0x10141400 as *const u8, 6164) }; | ||
| 50 | |||
| 51 | let pwr = Output::new(p.PIN_23, Level::Low); | ||
| 52 | let cs = Output::new(p.PIN_25, Level::High); | ||
| 53 | let mut pio = Pio::new(p.PIO0, Irqs); | ||
| 54 | let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); | ||
| 55 | |||
| 56 | static STATE: StaticCell<cyw43::State> = StaticCell::new(); | ||
| 57 | let state = STATE.init(cyw43::State::new()); | ||
| 58 | let (_net_device, bt_device, mut control, runner) = cyw43::new_with_bluetooth(state, pwr, spi, fw, btfw).await; | ||
| 59 | unwrap!(spawner.spawn(cyw43_task(runner))); | ||
| 60 | control.init(clm).await; | ||
| 61 | |||
| 62 | let controller: ExternalController<_, 10> = ExternalController::new(bt_device); | ||
| 63 | static HOST_RESOURCES: StaticCell<BleHostResources<4, 32, 27>> = StaticCell::new(); | ||
| 64 | let host_resources = HOST_RESOURCES.init(BleHostResources::new(PacketQos::None)); | ||
| 65 | |||
| 66 | let mut ble: BleHost<'_, _> = BleHost::new(controller, host_resources); | ||
| 67 | |||
| 68 | ble.set_random_address(Address::random([0xff, 0x9f, 0x1a, 0x05, 0xe4, 0xff])); | ||
| 69 | let mut table: AttributeTable<'_, NoopRawMutex, 10> = AttributeTable::new(); | ||
| 70 | |||
| 71 | // Generic Access Service (mandatory) | ||
| 72 | let id = b"Pico W Bluetooth"; | ||
| 73 | let appearance = [0x80, 0x07]; | ||
| 74 | let mut bat_level = [0; 1]; | ||
| 75 | let handle = { | ||
| 76 | let mut svc = table.add_service(Service::new(0x1800)); | ||
| 77 | let _ = svc.add_characteristic_ro(0x2a00, id); | ||
| 78 | let _ = svc.add_characteristic_ro(0x2a01, &appearance[..]); | ||
| 79 | svc.build(); | ||
| 80 | |||
| 81 | // Generic attribute service (mandatory) | ||
| 82 | table.add_service(Service::new(0x1801)); | ||
| 83 | |||
| 84 | // Battery service | ||
| 85 | let mut svc = table.add_service(Service::new(0x180f)); | ||
| 86 | |||
| 87 | svc.add_characteristic( | ||
| 88 | 0x2a19, | ||
| 89 | &[CharacteristicProp::Read, CharacteristicProp::Notify], | ||
| 90 | &mut bat_level, | ||
| 91 | ) | ||
| 92 | .build() | ||
| 93 | }; | ||
| 94 | |||
| 95 | let mut adv_data = [0; 31]; | ||
| 96 | AdStructure::encode_slice( | ||
| 97 | &[ | ||
| 98 | AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), | ||
| 99 | AdStructure::ServiceUuids16(&[Uuid::Uuid16([0x0f, 0x18])]), | ||
| 100 | AdStructure::CompleteLocalName(b"Pico W Bluetooth"), | ||
| 101 | ], | ||
| 102 | &mut adv_data[..], | ||
| 103 | ) | ||
| 104 | .unwrap(); | ||
| 105 | |||
| 106 | let server = ble.gatt_server(&table); | ||
| 107 | |||
| 108 | info!("Starting advertising and GATT service"); | ||
| 109 | let _ = join3( | ||
| 110 | ble.run(), | ||
| 111 | async { | ||
| 112 | loop { | ||
| 113 | match server.next().await { | ||
| 114 | Ok(GattEvent::Write { handle, connection: _ }) => { | ||
| 115 | let _ = table.get(handle, |value| { | ||
| 116 | info!("Write event. Value written: {:?}", value); | ||
| 117 | }); | ||
| 118 | } | ||
| 119 | Ok(GattEvent::Read { .. }) => { | ||
| 120 | info!("Read event"); | ||
| 121 | } | ||
| 122 | Err(e) => { | ||
| 123 | error!("Error processing GATT events: {:?}", e); | ||
| 124 | } | ||
| 125 | } | ||
| 126 | } | ||
| 127 | }, | ||
| 128 | async { | ||
| 129 | let mut advertiser = ble | ||
| 130 | .advertise( | ||
| 131 | &Default::default(), | ||
| 132 | Advertisement::ConnectableScannableUndirected { | ||
| 133 | adv_data: &adv_data[..], | ||
| 134 | scan_data: &[], | ||
| 135 | }, | ||
| 136 | ) | ||
| 137 | .await | ||
| 138 | .unwrap(); | ||
| 139 | let conn = advertiser.accept().await.unwrap(); | ||
| 140 | // Keep connection alive | ||
| 141 | let mut tick: u8 = 0; | ||
| 142 | loop { | ||
| 143 | Timer::after(Duration::from_secs(10)).await; | ||
| 144 | tick += 1; | ||
| 145 | server.notify(handle, &conn, &[tick]).await.unwrap(); | ||
| 146 | } | ||
| 147 | }, | ||
| 148 | ) | ||
| 149 | .await; | ||
| 150 | } | ||
diff --git a/examples/rp/src/bin/ethernet_w5500_multisocket.rs b/examples/rp/src/bin/ethernet_w5500_multisocket.rs index bd52cadca..12003adbe 100644 --- a/examples/rp/src/bin/ethernet_w5500_multisocket.rs +++ b/examples/rp/src/bin/ethernet_w5500_multisocket.rs | |||
| @@ -36,8 +36,8 @@ async fn ethernet_task( | |||
| 36 | } | 36 | } |
| 37 | 37 | ||
| 38 | #[embassy_executor::task] | 38 | #[embassy_executor::task] |
| 39 | async fn net_task(stack: &'static Stack<Device<'static>>) -> ! { | 39 | async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { |
| 40 | stack.run().await | 40 | runner.run().await |
| 41 | } | 41 | } |
| 42 | 42 | ||
| 43 | #[embassy_executor::main] | 43 | #[embassy_executor::main] |
| @@ -63,24 +63,24 @@ async fn main(spawner: Spawner) { | |||
| 63 | w5500_int, | 63 | w5500_int, |
| 64 | w5500_reset, | 64 | w5500_reset, |
| 65 | ) | 65 | ) |
| 66 | .await; | 66 | .await |
| 67 | .unwrap(); | ||
| 67 | unwrap!(spawner.spawn(ethernet_task(runner))); | 68 | unwrap!(spawner.spawn(ethernet_task(runner))); |
| 68 | 69 | ||
| 69 | // Generate random seed | 70 | // Generate random seed |
| 70 | let seed = rng.next_u64(); | 71 | let seed = rng.next_u64(); |
| 71 | 72 | ||
| 72 | // Init network stack | 73 | // Init network stack |
| 73 | static STACK: StaticCell<Stack<Device<'static>>> = StaticCell::new(); | ||
| 74 | static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new(); | 74 | static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new(); |
| 75 | let stack = &*STACK.init(Stack::new( | 75 | let (stack, runner) = embassy_net::new( |
| 76 | device, | 76 | device, |
| 77 | embassy_net::Config::dhcpv4(Default::default()), | 77 | embassy_net::Config::dhcpv4(Default::default()), |
| 78 | RESOURCES.init(StackResources::<3>::new()), | 78 | RESOURCES.init(StackResources::new()), |
| 79 | seed, | 79 | seed, |
| 80 | )); | 80 | ); |
| 81 | 81 | ||
| 82 | // Launch network task | 82 | // Launch network task |
| 83 | unwrap!(spawner.spawn(net_task(&stack))); | 83 | unwrap!(spawner.spawn(net_task(runner))); |
| 84 | 84 | ||
| 85 | info!("Waiting for DHCP..."); | 85 | info!("Waiting for DHCP..."); |
| 86 | let cfg = wait_for_config(stack).await; | 86 | let cfg = wait_for_config(stack).await; |
| @@ -88,12 +88,12 @@ async fn main(spawner: Spawner) { | |||
| 88 | info!("IP address: {:?}", local_addr); | 88 | info!("IP address: {:?}", local_addr); |
| 89 | 89 | ||
| 90 | // Create two sockets listening to the same port, to handle simultaneous connections | 90 | // Create two sockets listening to the same port, to handle simultaneous connections |
| 91 | unwrap!(spawner.spawn(listen_task(&stack, 0, 1234))); | 91 | unwrap!(spawner.spawn(listen_task(stack, 0, 1234))); |
| 92 | unwrap!(spawner.spawn(listen_task(&stack, 1, 1234))); | 92 | unwrap!(spawner.spawn(listen_task(stack, 1, 1234))); |
| 93 | } | 93 | } |
| 94 | 94 | ||
| 95 | #[embassy_executor::task(pool_size = 2)] | 95 | #[embassy_executor::task(pool_size = 2)] |
| 96 | async fn listen_task(stack: &'static Stack<Device<'static>>, id: u8, port: u16) { | 96 | async fn listen_task(stack: Stack<'static>, id: u8, port: u16) { |
| 97 | let mut rx_buffer = [0; 4096]; | 97 | let mut rx_buffer = [0; 4096]; |
| 98 | let mut tx_buffer = [0; 4096]; | 98 | let mut tx_buffer = [0; 4096]; |
| 99 | let mut buf = [0; 4096]; | 99 | let mut buf = [0; 4096]; |
| @@ -130,7 +130,7 @@ async fn listen_task(stack: &'static Stack<Device<'static>>, id: u8, port: u16) | |||
| 130 | } | 130 | } |
| 131 | } | 131 | } |
| 132 | 132 | ||
| 133 | async fn wait_for_config(stack: &'static Stack<Device<'static>>) -> embassy_net::StaticConfigV4 { | 133 | async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { |
| 134 | loop { | 134 | loop { |
| 135 | if let Some(config) = stack.config_v4() { | 135 | if let Some(config) = stack.config_v4() { |
| 136 | return config.clone(); | 136 | return config.clone(); |
diff --git a/examples/rp/src/bin/ethernet_w5500_tcp_client.rs b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs index 3e4fbd2e6..d66a43a88 100644 --- a/examples/rp/src/bin/ethernet_w5500_tcp_client.rs +++ b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs | |||
| @@ -38,8 +38,8 @@ async fn ethernet_task( | |||
| 38 | } | 38 | } |
| 39 | 39 | ||
| 40 | #[embassy_executor::task] | 40 | #[embassy_executor::task] |
| 41 | async fn net_task(stack: &'static Stack<Device<'static>>) -> ! { | 41 | async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { |
| 42 | stack.run().await | 42 | runner.run().await |
| 43 | } | 43 | } |
| 44 | 44 | ||
| 45 | #[embassy_executor::main] | 45 | #[embassy_executor::main] |
| @@ -66,24 +66,24 @@ async fn main(spawner: Spawner) { | |||
| 66 | w5500_int, | 66 | w5500_int, |
| 67 | w5500_reset, | 67 | w5500_reset, |
| 68 | ) | 68 | ) |
| 69 | .await; | 69 | .await |
| 70 | .unwrap(); | ||
| 70 | unwrap!(spawner.spawn(ethernet_task(runner))); | 71 | unwrap!(spawner.spawn(ethernet_task(runner))); |
| 71 | 72 | ||
| 72 | // Generate random seed | 73 | // Generate random seed |
| 73 | let seed = rng.next_u64(); | 74 | let seed = rng.next_u64(); |
| 74 | 75 | ||
| 75 | // Init network stack | 76 | // Init network stack |
| 76 | static STACK: StaticCell<Stack<Device<'static>>> = StaticCell::new(); | 77 | static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new(); |
| 77 | static RESOURCES: StaticCell<StackResources<2>> = StaticCell::new(); | 78 | let (stack, runner) = embassy_net::new( |
| 78 | let stack = &*STACK.init(Stack::new( | ||
| 79 | device, | 79 | device, |
| 80 | embassy_net::Config::dhcpv4(Default::default()), | 80 | embassy_net::Config::dhcpv4(Default::default()), |
| 81 | RESOURCES.init(StackResources::<2>::new()), | 81 | RESOURCES.init(StackResources::new()), |
| 82 | seed, | 82 | seed, |
| 83 | )); | 83 | ); |
| 84 | 84 | ||
| 85 | // Launch network task | 85 | // Launch network task |
| 86 | unwrap!(spawner.spawn(net_task(&stack))); | 86 | unwrap!(spawner.spawn(net_task(runner))); |
| 87 | 87 | ||
| 88 | info!("Waiting for DHCP..."); | 88 | info!("Waiting for DHCP..."); |
| 89 | let cfg = wait_for_config(stack).await; | 89 | let cfg = wait_for_config(stack).await; |
| @@ -118,7 +118,7 @@ async fn main(spawner: Spawner) { | |||
| 118 | } | 118 | } |
| 119 | } | 119 | } |
| 120 | 120 | ||
| 121 | async fn wait_for_config(stack: &'static Stack<Device<'static>>) -> embassy_net::StaticConfigV4 { | 121 | async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { |
| 122 | loop { | 122 | loop { |
| 123 | if let Some(config) = stack.config_v4() { | 123 | if let Some(config) = stack.config_v4() { |
| 124 | return config.clone(); | 124 | return config.clone(); |
diff --git a/examples/rp/src/bin/ethernet_w5500_tcp_server.rs b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs index 5532851f3..97d9bd4c9 100644 --- a/examples/rp/src/bin/ethernet_w5500_tcp_server.rs +++ b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs | |||
| @@ -37,8 +37,8 @@ async fn ethernet_task( | |||
| 37 | } | 37 | } |
| 38 | 38 | ||
| 39 | #[embassy_executor::task] | 39 | #[embassy_executor::task] |
| 40 | async fn net_task(stack: &'static Stack<Device<'static>>) -> ! { | 40 | async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { |
| 41 | stack.run().await | 41 | runner.run().await |
| 42 | } | 42 | } |
| 43 | 43 | ||
| 44 | #[embassy_executor::main] | 44 | #[embassy_executor::main] |
| @@ -65,24 +65,24 @@ async fn main(spawner: Spawner) { | |||
| 65 | w5500_int, | 65 | w5500_int, |
| 66 | w5500_reset, | 66 | w5500_reset, |
| 67 | ) | 67 | ) |
| 68 | .await; | 68 | .await |
| 69 | .unwrap(); | ||
| 69 | unwrap!(spawner.spawn(ethernet_task(runner))); | 70 | unwrap!(spawner.spawn(ethernet_task(runner))); |
| 70 | 71 | ||
| 71 | // Generate random seed | 72 | // Generate random seed |
| 72 | let seed = rng.next_u64(); | 73 | let seed = rng.next_u64(); |
| 73 | 74 | ||
| 74 | // Init network stack | 75 | // Init network stack |
| 75 | static STACK: StaticCell<Stack<Device<'static>>> = StaticCell::new(); | 76 | static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new(); |
| 76 | static RESOURCES: StaticCell<StackResources<2>> = StaticCell::new(); | 77 | let (stack, runner) = embassy_net::new( |
| 77 | let stack = &*STACK.init(Stack::new( | ||
| 78 | device, | 78 | device, |
| 79 | embassy_net::Config::dhcpv4(Default::default()), | 79 | embassy_net::Config::dhcpv4(Default::default()), |
| 80 | RESOURCES.init(StackResources::<2>::new()), | 80 | RESOURCES.init(StackResources::new()), |
| 81 | seed, | 81 | seed, |
| 82 | )); | 82 | ); |
| 83 | 83 | ||
| 84 | // Launch network task | 84 | // Launch network task |
| 85 | unwrap!(spawner.spawn(net_task(&stack))); | 85 | unwrap!(spawner.spawn(net_task(runner))); |
| 86 | 86 | ||
| 87 | info!("Waiting for DHCP..."); | 87 | info!("Waiting for DHCP..."); |
| 88 | let cfg = wait_for_config(stack).await; | 88 | let cfg = wait_for_config(stack).await; |
| @@ -127,7 +127,7 @@ async fn main(spawner: Spawner) { | |||
| 127 | } | 127 | } |
| 128 | } | 128 | } |
| 129 | 129 | ||
| 130 | async fn wait_for_config(stack: &'static Stack<Device<'static>>) -> embassy_net::StaticConfigV4 { | 130 | async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { |
| 131 | loop { | 131 | loop { |
| 132 | if let Some(config) = stack.config_v4() { | 132 | if let Some(config) = stack.config_v4() { |
| 133 | return config.clone(); | 133 | return config.clone(); |
diff --git a/examples/rp/src/bin/ethernet_w5500_udp.rs b/examples/rp/src/bin/ethernet_w5500_udp.rs index adb1d8941..b1b5f9758 100644 --- a/examples/rp/src/bin/ethernet_w5500_udp.rs +++ b/examples/rp/src/bin/ethernet_w5500_udp.rs | |||
| @@ -36,8 +36,8 @@ async fn ethernet_task( | |||
| 36 | } | 36 | } |
| 37 | 37 | ||
| 38 | #[embassy_executor::task] | 38 | #[embassy_executor::task] |
| 39 | async fn net_task(stack: &'static Stack<Device<'static>>) -> ! { | 39 | async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { |
| 40 | stack.run().await | 40 | runner.run().await |
| 41 | } | 41 | } |
| 42 | 42 | ||
| 43 | #[embassy_executor::main] | 43 | #[embassy_executor::main] |
| @@ -63,24 +63,24 @@ async fn main(spawner: Spawner) { | |||
| 63 | w5500_int, | 63 | w5500_int, |
| 64 | w5500_reset, | 64 | w5500_reset, |
| 65 | ) | 65 | ) |
| 66 | .await; | 66 | .await |
| 67 | .unwrap(); | ||
| 67 | unwrap!(spawner.spawn(ethernet_task(runner))); | 68 | unwrap!(spawner.spawn(ethernet_task(runner))); |
| 68 | 69 | ||
| 69 | // Generate random seed | 70 | // Generate random seed |
| 70 | let seed = rng.next_u64(); | 71 | let seed = rng.next_u64(); |
| 71 | 72 | ||
| 72 | // Init network stack | 73 | // Init network stack |
| 73 | static STACK: StaticCell<Stack<Device<'static>>> = StaticCell::new(); | 74 | static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new(); |
| 74 | static RESOURCES: StaticCell<StackResources<2>> = StaticCell::new(); | 75 | let (stack, runner) = embassy_net::new( |
| 75 | let stack = &*STACK.init(Stack::new( | ||
| 76 | device, | 76 | device, |
| 77 | embassy_net::Config::dhcpv4(Default::default()), | 77 | embassy_net::Config::dhcpv4(Default::default()), |
| 78 | RESOURCES.init(StackResources::<2>::new()), | 78 | RESOURCES.init(StackResources::new()), |
| 79 | seed, | 79 | seed, |
| 80 | )); | 80 | ); |
| 81 | 81 | ||
| 82 | // Launch network task | 82 | // Launch network task |
| 83 | unwrap!(spawner.spawn(net_task(&stack))); | 83 | unwrap!(spawner.spawn(net_task(runner))); |
| 84 | 84 | ||
| 85 | info!("Waiting for DHCP..."); | 85 | info!("Waiting for DHCP..."); |
| 86 | let cfg = wait_for_config(stack).await; | 86 | let cfg = wait_for_config(stack).await; |
| @@ -107,7 +107,7 @@ async fn main(spawner: Spawner) { | |||
| 107 | } | 107 | } |
| 108 | } | 108 | } |
| 109 | 109 | ||
| 110 | async fn wait_for_config(stack: &'static Stack<Device<'static>>) -> embassy_net::StaticConfigV4 { | 110 | async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { |
| 111 | loop { | 111 | loop { |
| 112 | if let Some(config) = stack.config_v4() { | 112 | if let Some(config) = stack.config_v4() { |
| 113 | return config.clone(); | 113 | return config.clone(); |
diff --git a/examples/rp/src/bin/i2c_async_embassy.rs b/examples/rp/src/bin/i2c_async_embassy.rs new file mode 100644 index 000000000..a65b71b9f --- /dev/null +++ b/examples/rp/src/bin/i2c_async_embassy.rs | |||
| @@ -0,0 +1,85 @@ | |||
| 1 | //! This example shows how to communicate asynchronous using i2c with external chip. | ||
| 2 | //! | ||
| 3 | //! It's using embassy's functions directly instead of traits from embedded_hal_async::i2c::I2c. | ||
| 4 | //! While most of i2c devices are addressed using 7 bits, an extension allows 10 bits too. | ||
| 5 | |||
| 6 | #![no_std] | ||
| 7 | #![no_main] | ||
| 8 | |||
| 9 | use defmt::*; | ||
| 10 | use embassy_rp::i2c::InterruptHandler; | ||
| 11 | use {defmt_rtt as _, panic_probe as _}; | ||
| 12 | |||
| 13 | // Our anonymous hypotetical temperature sensor could be: | ||
| 14 | // a 12-bit sensor, with 100ms startup time, range of -40*C - 125*C, and precision 0.25*C | ||
| 15 | // It requires no configuration or calibration, works with all i2c bus speeds, | ||
| 16 | // never stretches clock or does anything complicated. Replies with one u16. | ||
| 17 | // It requires only one write to take it out of suspend mode, and stays on. | ||
| 18 | // Often result would be just on 12 bits, but here we'll simplify it to 16. | ||
| 19 | |||
| 20 | enum UncomplicatedSensorId { | ||
| 21 | A(UncomplicatedSensorU8), | ||
| 22 | B(UncomplicatedSensorU16), | ||
| 23 | } | ||
| 24 | enum UncomplicatedSensorU8 { | ||
| 25 | First = 0x48, | ||
| 26 | } | ||
| 27 | enum UncomplicatedSensorU16 { | ||
| 28 | Other = 0x0049, | ||
| 29 | } | ||
| 30 | |||
| 31 | impl Into<u16> for UncomplicatedSensorU16 { | ||
| 32 | fn into(self) -> u16 { | ||
| 33 | self as u16 | ||
| 34 | } | ||
| 35 | } | ||
| 36 | impl Into<u16> for UncomplicatedSensorU8 { | ||
| 37 | fn into(self) -> u16 { | ||
| 38 | 0x48 | ||
| 39 | } | ||
| 40 | } | ||
| 41 | impl From<UncomplicatedSensorId> for u16 { | ||
| 42 | fn from(t: UncomplicatedSensorId) -> Self { | ||
| 43 | match t { | ||
| 44 | UncomplicatedSensorId::A(x) => x.into(), | ||
| 45 | UncomplicatedSensorId::B(x) => x.into(), | ||
| 46 | } | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | embassy_rp::bind_interrupts!(struct Irqs { | ||
| 51 | I2C1_IRQ => InterruptHandler<embassy_rp::peripherals::I2C1>; | ||
| 52 | }); | ||
| 53 | |||
| 54 | #[embassy_executor::main] | ||
| 55 | async fn main(_task_spawner: embassy_executor::Spawner) { | ||
| 56 | let p = embassy_rp::init(Default::default()); | ||
| 57 | let sda = p.PIN_14; | ||
| 58 | let scl = p.PIN_15; | ||
| 59 | let config = embassy_rp::i2c::Config::default(); | ||
| 60 | let mut bus = embassy_rp::i2c::I2c::new_async(p.I2C1, scl, sda, Irqs, config); | ||
| 61 | |||
| 62 | const WAKEYWAKEY: u16 = 0xBABE; | ||
| 63 | let mut result: [u8; 2] = [0, 0]; | ||
| 64 | // wait for sensors to initialize | ||
| 65 | embassy_time::Timer::after(embassy_time::Duration::from_millis(100)).await; | ||
| 66 | |||
| 67 | let _res_1 = bus | ||
| 68 | .write_async(UncomplicatedSensorU8::First, WAKEYWAKEY.to_be_bytes()) | ||
| 69 | .await; | ||
| 70 | let _res_2 = bus | ||
| 71 | .write_async(UncomplicatedSensorU16::Other, WAKEYWAKEY.to_be_bytes()) | ||
| 72 | .await; | ||
| 73 | |||
| 74 | loop { | ||
| 75 | let s1 = UncomplicatedSensorId::A(UncomplicatedSensorU8::First); | ||
| 76 | let s2 = UncomplicatedSensorId::B(UncomplicatedSensorU16::Other); | ||
| 77 | let sensors = [s1, s2]; | ||
| 78 | for sensor in sensors { | ||
| 79 | if bus.read_async(sensor, &mut result).await.is_ok() { | ||
| 80 | info!("Result {}", u16::from_be_bytes(result.into())); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | embassy_time::Timer::after(embassy_time::Duration::from_millis(200)).await; | ||
| 84 | } | ||
| 85 | } | ||
diff --git a/examples/rp/src/bin/i2c_slave.rs b/examples/rp/src/bin/i2c_slave.rs index ac470d2be..9fffb4646 100644 --- a/examples/rp/src/bin/i2c_slave.rs +++ b/examples/rp/src/bin/i2c_slave.rs | |||
| @@ -110,7 +110,7 @@ async fn main(spawner: Spawner) { | |||
| 110 | let c_sda = p.PIN_1; | 110 | let c_sda = p.PIN_1; |
| 111 | let c_scl = p.PIN_0; | 111 | let c_scl = p.PIN_0; |
| 112 | let mut config = i2c::Config::default(); | 112 | let mut config = i2c::Config::default(); |
| 113 | config.frequency = 5_000; | 113 | config.frequency = 1_000_000; |
| 114 | let controller = i2c::I2c::new_async(p.I2C0, c_sda, c_scl, Irqs, config); | 114 | let controller = i2c::I2c::new_async(p.I2C0, c_sda, c_scl, Irqs, config); |
| 115 | 115 | ||
| 116 | unwrap!(spawner.spawn(controller_task(controller))); | 116 | unwrap!(spawner.spawn(controller_task(controller))); |
diff --git a/examples/rp/src/bin/interrupt.rs b/examples/rp/src/bin/interrupt.rs new file mode 100644 index 000000000..5b9d7027e --- /dev/null +++ b/examples/rp/src/bin/interrupt.rs | |||
| @@ -0,0 +1,94 @@ | |||
| 1 | //! This example shows how you can use raw interrupt handlers alongside embassy. | ||
| 2 | //! The example also showcases some of the options available for sharing resources/data. | ||
| 3 | //! | ||
| 4 | //! In the example, an ADC reading is triggered every time the PWM wraps around. | ||
| 5 | //! The sample data is sent down a channel, to be processed inside a low priority task. | ||
| 6 | //! The processed data is then used to adjust the PWM duty cycle, once every second. | ||
| 7 | |||
| 8 | #![no_std] | ||
| 9 | #![no_main] | ||
| 10 | |||
| 11 | use core::cell::{Cell, RefCell}; | ||
| 12 | |||
| 13 | use defmt::*; | ||
| 14 | use embassy_executor::Spawner; | ||
| 15 | use embassy_rp::adc::{self, Adc, Blocking}; | ||
| 16 | use embassy_rp::gpio::Pull; | ||
| 17 | use embassy_rp::interrupt; | ||
| 18 | use embassy_rp::pwm::{Config, Pwm}; | ||
| 19 | use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; | ||
| 20 | use embassy_sync::blocking_mutex::Mutex; | ||
| 21 | use embassy_sync::channel::Channel; | ||
| 22 | use embassy_time::{Duration, Ticker}; | ||
| 23 | use portable_atomic::{AtomicU32, Ordering}; | ||
| 24 | use static_cell::StaticCell; | ||
| 25 | use {defmt_rtt as _, panic_probe as _}; | ||
| 26 | |||
| 27 | static COUNTER: AtomicU32 = AtomicU32::new(0); | ||
| 28 | static PWM: Mutex<CriticalSectionRawMutex, RefCell<Option<Pwm>>> = Mutex::new(RefCell::new(None)); | ||
| 29 | static ADC: Mutex<CriticalSectionRawMutex, RefCell<Option<(Adc<Blocking>, adc::Channel)>>> = | ||
| 30 | Mutex::new(RefCell::new(None)); | ||
| 31 | static ADC_VALUES: Channel<CriticalSectionRawMutex, u16, 2048> = Channel::new(); | ||
| 32 | |||
| 33 | #[embassy_executor::main] | ||
| 34 | async fn main(spawner: Spawner) { | ||
| 35 | embassy_rp::pac::SIO.spinlock(31).write_value(1); | ||
| 36 | let p = embassy_rp::init(Default::default()); | ||
| 37 | |||
| 38 | let adc = Adc::new_blocking(p.ADC, Default::default()); | ||
| 39 | let p26 = adc::Channel::new_pin(p.PIN_26, Pull::None); | ||
| 40 | ADC.lock(|a| a.borrow_mut().replace((adc, p26))); | ||
| 41 | |||
| 42 | let pwm = Pwm::new_output_b(p.PWM_SLICE4, p.PIN_25, Default::default()); | ||
| 43 | PWM.lock(|p| p.borrow_mut().replace(pwm)); | ||
| 44 | |||
| 45 | // Enable the interrupt for pwm slice 4 | ||
| 46 | embassy_rp::pac::PWM.inte().modify(|w| w.set_ch4(true)); | ||
| 47 | unsafe { | ||
| 48 | cortex_m::peripheral::NVIC::unmask(interrupt::PWM_IRQ_WRAP); | ||
| 49 | } | ||
| 50 | |||
| 51 | // Tasks require their resources to have 'static lifetime | ||
| 52 | // No Mutex needed when sharing within the same executor/prio level | ||
| 53 | static AVG: StaticCell<Cell<u32>> = StaticCell::new(); | ||
| 54 | let avg = AVG.init(Default::default()); | ||
| 55 | spawner.must_spawn(processing(avg)); | ||
| 56 | |||
| 57 | let mut ticker = Ticker::every(Duration::from_secs(1)); | ||
| 58 | loop { | ||
| 59 | ticker.next().await; | ||
| 60 | let freq = COUNTER.swap(0, Ordering::Relaxed); | ||
| 61 | info!("pwm freq: {:?} Hz", freq); | ||
| 62 | info!("adc average: {:?}", avg.get()); | ||
| 63 | |||
| 64 | // Update the pwm duty cycle, based on the averaged adc reading | ||
| 65 | let mut config = Config::default(); | ||
| 66 | config.compare_b = ((avg.get() as f32 / 4095.0) * config.top as f32) as _; | ||
| 67 | PWM.lock(|p| p.borrow_mut().as_mut().unwrap().set_config(&config)); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | #[embassy_executor::task] | ||
| 72 | async fn processing(avg: &'static Cell<u32>) { | ||
| 73 | let mut buffer: heapless::HistoryBuffer<u16, 100> = Default::default(); | ||
| 74 | loop { | ||
| 75 | let val = ADC_VALUES.receive().await; | ||
| 76 | buffer.write(val); | ||
| 77 | let sum: u32 = buffer.iter().map(|x| *x as u32).sum(); | ||
| 78 | avg.set(sum / buffer.len() as u32); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | #[interrupt] | ||
| 83 | fn PWM_IRQ_WRAP() { | ||
| 84 | critical_section::with(|cs| { | ||
| 85 | let mut adc = ADC.borrow(cs).borrow_mut(); | ||
| 86 | let (adc, p26) = adc.as_mut().unwrap(); | ||
| 87 | let val = adc.blocking_read(p26).unwrap(); | ||
| 88 | ADC_VALUES.try_send(val).ok(); | ||
| 89 | |||
| 90 | // Clear the interrupt, so we don't immediately re-enter this irq handler | ||
| 91 | PWM.borrow(cs).borrow_mut().as_mut().unwrap().clear_wrapped(); | ||
| 92 | }); | ||
| 93 | COUNTER.fetch_add(1, Ordering::Relaxed); | ||
| 94 | } | ||
diff --git a/examples/rp/src/bin/multicore.rs b/examples/rp/src/bin/multicore.rs index c7b087476..7cb546c91 100644 --- a/examples/rp/src/bin/multicore.rs +++ b/examples/rp/src/bin/multicore.rs | |||
| @@ -30,10 +30,14 @@ fn main() -> ! { | |||
| 30 | let p = embassy_rp::init(Default::default()); | 30 | let p = embassy_rp::init(Default::default()); |
| 31 | let led = Output::new(p.PIN_25, Level::Low); | 31 | let led = Output::new(p.PIN_25, Level::Low); |
| 32 | 32 | ||
| 33 | spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || { | 33 | spawn_core1( |
| 34 | let executor1 = EXECUTOR1.init(Executor::new()); | 34 | p.CORE1, |
| 35 | executor1.run(|spawner| unwrap!(spawner.spawn(core1_task(led)))); | 35 | unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, |
| 36 | }); | 36 | move || { |
| 37 | let executor1 = EXECUTOR1.init(Executor::new()); | ||
| 38 | executor1.run(|spawner| unwrap!(spawner.spawn(core1_task(led)))); | ||
| 39 | }, | ||
| 40 | ); | ||
| 37 | 41 | ||
| 38 | let executor0 = EXECUTOR0.init(Executor::new()); | 42 | let executor0 = EXECUTOR0.init(Executor::new()); |
| 39 | executor0.run(|spawner| unwrap!(spawner.spawn(core0_task()))); | 43 | executor0.run(|spawner| unwrap!(spawner.spawn(core0_task()))); |
diff --git a/examples/rp/src/bin/multiprio.rs b/examples/rp/src/bin/multiprio.rs index 26b80c11d..2b397f97d 100644 --- a/examples/rp/src/bin/multiprio.rs +++ b/examples/rp/src/bin/multiprio.rs | |||
| @@ -80,7 +80,7 @@ async fn run_med() { | |||
| 80 | info!(" [med] Starting long computation"); | 80 | info!(" [med] Starting long computation"); |
| 81 | 81 | ||
| 82 | // Spin-wait to simulate a long CPU computation | 82 | // Spin-wait to simulate a long CPU computation |
| 83 | cortex_m::asm::delay(125_000_000); // ~1 second | 83 | embassy_time::block_for(embassy_time::Duration::from_secs(1)); // ~1 second |
| 84 | 84 | ||
| 85 | let end = Instant::now(); | 85 | let end = Instant::now(); |
| 86 | let ms = end.duration_since(start).as_ticks() * 1000 / TICK_HZ; | 86 | let ms = end.duration_since(start).as_ticks() * 1000 / TICK_HZ; |
| @@ -97,7 +97,7 @@ async fn run_low() { | |||
| 97 | info!("[low] Starting long computation"); | 97 | info!("[low] Starting long computation"); |
| 98 | 98 | ||
| 99 | // Spin-wait to simulate a long CPU computation | 99 | // Spin-wait to simulate a long CPU computation |
| 100 | cortex_m::asm::delay(250_000_000); // ~2 seconds | 100 | embassy_time::block_for(embassy_time::Duration::from_secs(2)); // ~2 seconds |
| 101 | 101 | ||
| 102 | let end = Instant::now(); | 102 | let end = Instant::now(); |
| 103 | let ms = end.duration_since(start).as_ticks() * 1000 / TICK_HZ; | 103 | let ms = end.duration_since(start).as_ticks() * 1000 / TICK_HZ; |
diff --git a/examples/rp/src/bin/orchestrate_tasks.rs b/examples/rp/src/bin/orchestrate_tasks.rs new file mode 100644 index 000000000..0e21d5833 --- /dev/null +++ b/examples/rp/src/bin/orchestrate_tasks.rs | |||
| @@ -0,0 +1,318 @@ | |||
| 1 | //! This example demonstrates some approaches to communicate between tasks in order to orchestrate the state of the system. | ||
| 2 | //! | ||
| 3 | //! We demonstrate how to: | ||
| 4 | //! - use a channel to send messages between tasks, in this case here in order to have one task control the state of the system. | ||
| 5 | //! - use a signal to terminate a task. | ||
| 6 | //! - use command channels to send commands to another task. | ||
| 7 | //! - use different ways to receive messages, from a straightforwar awaiting on one channel to a more complex awaiting on multiple futures. | ||
| 8 | //! | ||
| 9 | //! There are more patterns to orchestrate tasks, this is just one example. | ||
| 10 | //! | ||
| 11 | //! We will use these tasks to generate example "state information": | ||
| 12 | //! - a task that generates random numbers in intervals of 60s | ||
| 13 | //! - a task that generates random numbers in intervals of 30s | ||
| 14 | //! - a task that generates random numbers in intervals of 90s | ||
| 15 | //! - a task that notifies about being attached/disattached from usb power | ||
| 16 | //! - a task that measures vsys voltage in intervals of 30s | ||
| 17 | //! - a task that consumes the state information and reacts to it | ||
| 18 | |||
| 19 | #![no_std] | ||
| 20 | #![no_main] | ||
| 21 | |||
| 22 | use assign_resources::assign_resources; | ||
| 23 | use defmt::*; | ||
| 24 | use embassy_executor::Spawner; | ||
| 25 | use embassy_futures::select::{select, Either}; | ||
| 26 | use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler}; | ||
| 27 | use embassy_rp::clocks::RoscRng; | ||
| 28 | use embassy_rp::gpio::{Input, Pull}; | ||
| 29 | use embassy_rp::{bind_interrupts, peripherals}; | ||
| 30 | use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; | ||
| 31 | use embassy_sync::{channel, signal}; | ||
| 32 | use embassy_time::{Duration, Timer}; | ||
| 33 | use rand::RngCore; | ||
| 34 | use {defmt_rtt as _, panic_probe as _}; | ||
| 35 | |||
| 36 | // This is just some preparation, see example `assign_resources.rs` for more information on this. We prep the rresources that we will be using in different tasks. | ||
| 37 | // **Note**: This will not work with a board that has a wifi chip, because the wifi chip uses pins 24 and 29 for its own purposes. A way around this in software | ||
| 38 | // is not trivial, at least if you intend to use wifi, too. Workaround is to wire from vsys and vbus pins to appropriate pins on the board through a voltage divider. Then use those pins. | ||
| 39 | // For this example it will not matter much, the concept of what we are showing remains valid. | ||
| 40 | assign_resources! { | ||
| 41 | vsys: Vsys { | ||
| 42 | adc: ADC, | ||
| 43 | pin_29: PIN_29, | ||
| 44 | }, | ||
| 45 | vbus: Vbus { | ||
| 46 | pin_24: PIN_24, | ||
| 47 | }, | ||
| 48 | } | ||
| 49 | |||
| 50 | bind_interrupts!(struct Irqs { | ||
| 51 | ADC_IRQ_FIFO => InterruptHandler; | ||
| 52 | }); | ||
| 53 | |||
| 54 | /// This is the type of Events that we will send from the worker tasks to the orchestrating task. | ||
| 55 | enum Events { | ||
| 56 | UsbPowered(bool), | ||
| 57 | VsysVoltage(f32), | ||
| 58 | FirstRandomSeed(u32), | ||
| 59 | SecondRandomSeed(u32), | ||
| 60 | ThirdRandomSeed(u32), | ||
| 61 | ResetFirstRandomSeed, | ||
| 62 | } | ||
| 63 | |||
| 64 | /// This is the type of Commands that we will send from the orchestrating task to the worker tasks. | ||
| 65 | /// Note that we are lazy here and only have one command, you might want to have more. | ||
| 66 | enum Commands { | ||
| 67 | /// This command will stop the appropriate worker task | ||
| 68 | Stop, | ||
| 69 | } | ||
| 70 | |||
| 71 | /// This is the state of the system, we will use this to orchestrate the system. This is a simple example, in a real world application this would be more complex. | ||
| 72 | #[derive(Default, Debug, Clone, Format)] | ||
| 73 | struct State { | ||
| 74 | usb_powered: bool, | ||
| 75 | vsys_voltage: f32, | ||
| 76 | first_random_seed: u32, | ||
| 77 | second_random_seed: u32, | ||
| 78 | third_random_seed: u32, | ||
| 79 | times_we_got_first_random_seed: u8, | ||
| 80 | maximum_times_we_want_first_random_seed: u8, | ||
| 81 | } | ||
| 82 | |||
| 83 | impl State { | ||
| 84 | fn new() -> Self { | ||
| 85 | Self { | ||
| 86 | usb_powered: false, | ||
| 87 | vsys_voltage: 0.0, | ||
| 88 | first_random_seed: 0, | ||
| 89 | second_random_seed: 0, | ||
| 90 | third_random_seed: 0, | ||
| 91 | times_we_got_first_random_seed: 0, | ||
| 92 | maximum_times_we_want_first_random_seed: 3, | ||
| 93 | } | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | /// Channel for the events that we want the orchestrator to react to, all state events are of the type Enum Events. | ||
| 98 | /// We use a channel with an arbitrary size of 10, the precise size of the queue depends on your use case. This depends on how many events we | ||
| 99 | /// expect to be generated in a given time frame and how fast the orchestrator can react to them. And then if we rather want the senders to wait for | ||
| 100 | /// new slots in the queue or if we want the orchestrator to have a backlog of events to process. In this case here we expect to always be enough slots | ||
| 101 | /// in the queue, so the worker tasks can in all nominal cases send their events and continue with their work without waiting. | ||
| 102 | /// For the events we - in this case here - do not want to loose any events, so a channel is a good choice. See embassy_sync docs for other options. | ||
| 103 | static EVENT_CHANNEL: channel::Channel<CriticalSectionRawMutex, Events, 10> = channel::Channel::new(); | ||
| 104 | |||
| 105 | /// Signal for stopping the first random signal task. We use a signal here, because we need no queue. It is suffiient to have one signal active. | ||
| 106 | static STOP_FIRST_RANDOM_SIGNAL: signal::Signal<CriticalSectionRawMutex, Commands> = signal::Signal::new(); | ||
| 107 | |||
| 108 | /// Channel for the state that we want the consumer task to react to. We use a channel here, because we want to have a queue of state changes, although | ||
| 109 | /// we want the queue to be of size 1, because we want to finish rwacting to the state change before the next one comes in. This is just a design choice | ||
| 110 | /// and depends on your use case. | ||
| 111 | static CONSUMER_CHANNEL: channel::Channel<CriticalSectionRawMutex, State, 1> = channel::Channel::new(); | ||
| 112 | |||
| 113 | // And now we can put all this into use | ||
| 114 | |||
| 115 | /// This is the main task, that will not do very much besides spawning the other tasks. This is a design choice, you could do the | ||
| 116 | /// orchestrating here. This is to show that we do not need a main loop here, the system will run indefinitely as long as at least one task is running. | ||
| 117 | #[embassy_executor::main] | ||
| 118 | async fn main(spawner: Spawner) { | ||
| 119 | // initialize the peripherals | ||
| 120 | let p = embassy_rp::init(Default::default()); | ||
| 121 | // split the resources, for convenience - see above | ||
| 122 | let r = split_resources! {p}; | ||
| 123 | |||
| 124 | // spawn the tasks | ||
| 125 | spawner.spawn(orchestrate(spawner)).unwrap(); | ||
| 126 | spawner.spawn(random_60s(spawner)).unwrap(); | ||
| 127 | spawner.spawn(random_90s(spawner)).unwrap(); | ||
| 128 | spawner.spawn(usb_power(spawner, r.vbus)).unwrap(); | ||
| 129 | spawner.spawn(vsys_voltage(spawner, r.vsys)).unwrap(); | ||
| 130 | spawner.spawn(consumer(spawner)).unwrap(); | ||
| 131 | } | ||
| 132 | |||
| 133 | /// This is the task handling the system state and orchestrating the other tasks. WEe can regard this as the "main loop" of the system. | ||
| 134 | #[embassy_executor::task] | ||
| 135 | async fn orchestrate(_spawner: Spawner) { | ||
| 136 | let mut state = State::new(); | ||
| 137 | |||
| 138 | // we need to have a receiver for the events | ||
| 139 | let receiver = EVENT_CHANNEL.receiver(); | ||
| 140 | |||
| 141 | // and we need a sender for the consumer task | ||
| 142 | let state_sender = CONSUMER_CHANNEL.sender(); | ||
| 143 | |||
| 144 | loop { | ||
| 145 | // we await on the receiver, this will block until a new event is available | ||
| 146 | // as an alternative to this, we could also await on multiple channels, this would block until at least one of the channels has an event | ||
| 147 | // see the embassy_futures docs: https://docs.embassy.dev/embassy-futures/git/default/select/index.html | ||
| 148 | // The task random_30s does a select, if you want to have a look at that. | ||
| 149 | // Another reason to use select may also be that we want to have a timeout, so we can react to the absence of events within a time frame. | ||
| 150 | // We keep it simple here. | ||
| 151 | let event = receiver.receive().await; | ||
| 152 | |||
| 153 | // react to the events | ||
| 154 | match event { | ||
| 155 | Events::UsbPowered(usb_powered) => { | ||
| 156 | // update the state and/or react to the event here | ||
| 157 | state.usb_powered = usb_powered; | ||
| 158 | info!("Usb powered: {}", usb_powered); | ||
| 159 | } | ||
| 160 | Events::VsysVoltage(voltage) => { | ||
| 161 | // update the state and/or react to the event here | ||
| 162 | state.vsys_voltage = voltage; | ||
| 163 | info!("Vsys voltage: {}", voltage); | ||
| 164 | } | ||
| 165 | Events::FirstRandomSeed(seed) => { | ||
| 166 | // update the state and/or react to the event here | ||
| 167 | state.first_random_seed = seed; | ||
| 168 | // here we change some meta state, we count how many times we got the first random seed | ||
| 169 | state.times_we_got_first_random_seed += 1; | ||
| 170 | info!( | ||
| 171 | "First random seed: {}, and that was iteration {} of receiving this.", | ||
| 172 | seed, &state.times_we_got_first_random_seed | ||
| 173 | ); | ||
| 174 | } | ||
| 175 | Events::SecondRandomSeed(seed) => { | ||
| 176 | // update the state and/or react to the event here | ||
| 177 | state.second_random_seed = seed; | ||
| 178 | info!("Second random seed: {}", seed); | ||
| 179 | } | ||
| 180 | Events::ThirdRandomSeed(seed) => { | ||
| 181 | // update the state and/or react to the event here | ||
| 182 | state.third_random_seed = seed; | ||
| 183 | info!("Third random seed: {}", seed); | ||
| 184 | } | ||
| 185 | Events::ResetFirstRandomSeed => { | ||
| 186 | // update the state and/or react to the event here | ||
| 187 | state.times_we_got_first_random_seed = 0; | ||
| 188 | state.first_random_seed = 0; | ||
| 189 | info!("Resetting the first random seed counter"); | ||
| 190 | } | ||
| 191 | } | ||
| 192 | // we now have an altered state | ||
| 193 | // there is a crate for detecting field changes on crates.io (https://crates.io/crates/fieldset) that might be useful here | ||
| 194 | // for now we just keep it simple | ||
| 195 | |||
| 196 | // we send the state to the consumer task | ||
| 197 | // since the channel has a size of 1, this will block until the consumer task has received the state, which is what we want here in this example | ||
| 198 | // **Note:** It is bad design to send too much data between tasks, with no clear definition of what "too much" is. In this example we send the | ||
| 199 | // whole state, in a real world application you might want to send only the data, that is relevant to the consumer task AND only when it has changed. | ||
| 200 | // We keep it simple here. | ||
| 201 | state_sender.send(state.clone()).await; | ||
| 202 | } | ||
| 203 | } | ||
| 204 | |||
| 205 | /// This task will consume the state information and react to it. This is a simple example, in a real world application this would be more complex | ||
| 206 | /// and we could have multiple consumer tasks, each reacting to different parts of the state. | ||
| 207 | #[embassy_executor::task] | ||
| 208 | async fn consumer(spawner: Spawner) { | ||
| 209 | // we need to have a receiver for the state | ||
| 210 | let receiver = CONSUMER_CHANNEL.receiver(); | ||
| 211 | let sender = EVENT_CHANNEL.sender(); | ||
| 212 | loop { | ||
| 213 | // we await on the receiver, this will block until a new state is available | ||
| 214 | let state = receiver.receive().await; | ||
| 215 | // react to the state, in this case here we just log it | ||
| 216 | info!("The consumer has reveived this state: {:?}", &state); | ||
| 217 | |||
| 218 | // here we react to the state, in this case here we want to start or stop the first random signal task depending on the state of the system | ||
| 219 | match state.times_we_got_first_random_seed { | ||
| 220 | max if max == state.maximum_times_we_want_first_random_seed => { | ||
| 221 | info!("Stopping the first random signal task"); | ||
| 222 | // we send a command to the task | ||
| 223 | STOP_FIRST_RANDOM_SIGNAL.signal(Commands::Stop); | ||
| 224 | // we notify the orchestrator that we have sent the command | ||
| 225 | sender.send(Events::ResetFirstRandomSeed).await; | ||
| 226 | } | ||
| 227 | 0 => { | ||
| 228 | // we start the task, which presents us with an interesting problem, because we may return here before the task has started | ||
| 229 | // here we just try and log if the task has started, in a real world application you might want to handle this more gracefully | ||
| 230 | info!("Starting the first random signal task"); | ||
| 231 | match spawner.spawn(random_30s(spawner)) { | ||
| 232 | Ok(_) => info!("Successfully spawned random_30s task"), | ||
| 233 | Err(e) => info!("Failed to spawn random_30s task: {:?}", e), | ||
| 234 | } | ||
| 235 | } | ||
| 236 | _ => {} | ||
| 237 | } | ||
| 238 | } | ||
| 239 | } | ||
| 240 | |||
| 241 | /// This task will generate random numbers in intervals of 30s | ||
| 242 | /// The task will terminate after it has received a command signal to stop, see the orchestrate task for that. | ||
| 243 | /// Note that we are not spawning this task from main, as we will show how such a task can be spawned and closed dynamically. | ||
| 244 | #[embassy_executor::task] | ||
| 245 | async fn random_30s(_spawner: Spawner) { | ||
| 246 | let mut rng = RoscRng; | ||
| 247 | let sender = EVENT_CHANNEL.sender(); | ||
| 248 | loop { | ||
| 249 | // we either await on the timer or the signal, whichever comes first. | ||
| 250 | let futures = select(Timer::after(Duration::from_secs(30)), STOP_FIRST_RANDOM_SIGNAL.wait()).await; | ||
| 251 | match futures { | ||
| 252 | Either::First(_) => { | ||
| 253 | // we received are operating on the timer | ||
| 254 | info!("30s are up, generating random number"); | ||
| 255 | let random_number = rng.next_u32(); | ||
| 256 | sender.send(Events::FirstRandomSeed(random_number)).await; | ||
| 257 | } | ||
| 258 | Either::Second(_) => { | ||
| 259 | // we received the signal to stop | ||
| 260 | info!("Received signal to stop, goodbye!"); | ||
| 261 | break; | ||
| 262 | } | ||
| 263 | } | ||
| 264 | } | ||
| 265 | } | ||
| 266 | |||
| 267 | /// This task will generate random numbers in intervals of 60s | ||
| 268 | #[embassy_executor::task] | ||
| 269 | async fn random_60s(_spawner: Spawner) { | ||
| 270 | let mut rng = RoscRng; | ||
| 271 | let sender = EVENT_CHANNEL.sender(); | ||
| 272 | loop { | ||
| 273 | Timer::after(Duration::from_secs(60)).await; | ||
| 274 | let random_number = rng.next_u32(); | ||
| 275 | sender.send(Events::SecondRandomSeed(random_number)).await; | ||
| 276 | } | ||
| 277 | } | ||
| 278 | |||
| 279 | /// This task will generate random numbers in intervals of 90s | ||
| 280 | #[embassy_executor::task] | ||
| 281 | async fn random_90s(_spawner: Spawner) { | ||
| 282 | let mut rng = RoscRng; | ||
| 283 | let sender = EVENT_CHANNEL.sender(); | ||
| 284 | loop { | ||
| 285 | Timer::after(Duration::from_secs(90)).await; | ||
| 286 | let random_number = rng.next_u32(); | ||
| 287 | sender.send(Events::ThirdRandomSeed(random_number)).await; | ||
| 288 | } | ||
| 289 | } | ||
| 290 | |||
| 291 | /// This task will notify if we are connected to usb power | ||
| 292 | #[embassy_executor::task] | ||
| 293 | pub async fn usb_power(_spawner: Spawner, r: Vbus) { | ||
| 294 | let mut vbus_in = Input::new(r.pin_24, Pull::None); | ||
| 295 | let sender = EVENT_CHANNEL.sender(); | ||
| 296 | loop { | ||
| 297 | sender.send(Events::UsbPowered(vbus_in.is_high())).await; | ||
| 298 | vbus_in.wait_for_any_edge().await; | ||
| 299 | } | ||
| 300 | } | ||
| 301 | |||
| 302 | /// This task will measure the vsys voltage in intervals of 30s | ||
| 303 | #[embassy_executor::task] | ||
| 304 | pub async fn vsys_voltage(_spawner: Spawner, r: Vsys) { | ||
| 305 | let mut adc = Adc::new(r.adc, Irqs, Config::default()); | ||
| 306 | let vsys_in = r.pin_29; | ||
| 307 | let mut channel = Channel::new_pin(vsys_in, Pull::None); | ||
| 308 | let sender = EVENT_CHANNEL.sender(); | ||
| 309 | loop { | ||
| 310 | // read the adc value | ||
| 311 | let adc_value = adc.read(&mut channel).await.unwrap(); | ||
| 312 | // convert the adc value to voltage. | ||
| 313 | // 3.3 is the reference voltage, 3.0 is the factor for the inbuilt voltage divider and 4096 is the resolution of the adc | ||
| 314 | let voltage = (adc_value as f32) * 3.3 * 3.0 / 4096.0; | ||
| 315 | sender.send(Events::VsysVoltage(voltage)).await; | ||
| 316 | Timer::after(Duration::from_secs(30)).await; | ||
| 317 | } | ||
| 318 | } | ||
diff --git a/examples/rp/src/bin/pio_hd44780.rs b/examples/rp/src/bin/pio_hd44780.rs index 3fab7b5f2..6c02630e0 100644 --- a/examples/rp/src/bin/pio_hd44780.rs +++ b/examples/rp/src/bin/pio_hd44780.rs | |||
| @@ -35,7 +35,7 @@ async fn main(_spawner: Spawner) { | |||
| 35 | // allowing direct connection of the display to the RP2040 without level shifters. | 35 | // allowing direct connection of the display to the RP2040 without level shifters. |
| 36 | let p = embassy_rp::init(Default::default()); | 36 | let p = embassy_rp::init(Default::default()); |
| 37 | 37 | ||
| 38 | let _pwm = Pwm::new_output_b(p.PWM_CH7, p.PIN_15, { | 38 | let _pwm = Pwm::new_output_b(p.PWM_SLICE7, p.PIN_15, { |
| 39 | let mut c = pwm::Config::default(); | 39 | let mut c = pwm::Config::default(); |
| 40 | c.divider = 125.into(); | 40 | c.divider = 125.into(); |
| 41 | c.top = 100; | 41 | c.top = 100; |
diff --git a/examples/rp/src/bin/pio_onewire.rs b/examples/rp/src/bin/pio_onewire.rs new file mode 100644 index 000000000..5076101ec --- /dev/null +++ b/examples/rp/src/bin/pio_onewire.rs | |||
| @@ -0,0 +1,155 @@ | |||
| 1 | //! This example shows how you can use PIO to read a `DS18B20` one-wire temperature sensor. | ||
| 2 | |||
| 3 | #![no_std] | ||
| 4 | #![no_main] | ||
| 5 | use defmt::*; | ||
| 6 | use embassy_executor::Spawner; | ||
| 7 | use embassy_rp::bind_interrupts; | ||
| 8 | use embassy_rp::peripherals::PIO0; | ||
| 9 | use embassy_rp::pio::{self, Common, Config, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine}; | ||
| 10 | use embassy_time::Timer; | ||
| 11 | use {defmt_rtt as _, panic_probe as _}; | ||
| 12 | |||
| 13 | bind_interrupts!(struct Irqs { | ||
| 14 | PIO0_IRQ_0 => InterruptHandler<PIO0>; | ||
| 15 | }); | ||
| 16 | |||
| 17 | #[embassy_executor::main] | ||
| 18 | async fn main(_spawner: Spawner) { | ||
| 19 | let p = embassy_rp::init(Default::default()); | ||
| 20 | let mut pio = Pio::new(p.PIO0, Irqs); | ||
| 21 | let mut sensor = Ds18b20::new(&mut pio.common, pio.sm0, p.PIN_2); | ||
| 22 | |||
| 23 | loop { | ||
| 24 | sensor.start().await; // Start a new measurement | ||
| 25 | Timer::after_secs(1).await; // Allow 1s for the measurement to finish | ||
| 26 | match sensor.temperature().await { | ||
| 27 | Ok(temp) => info!("temp = {:?} deg C", temp), | ||
| 28 | _ => error!("sensor error"), | ||
| 29 | } | ||
| 30 | Timer::after_secs(1).await; | ||
| 31 | } | ||
| 32 | } | ||
| 33 | |||
| 34 | /// DS18B20 temperature sensor driver | ||
| 35 | pub struct Ds18b20<'d, PIO: pio::Instance, const SM: usize> { | ||
| 36 | sm: StateMachine<'d, PIO, SM>, | ||
| 37 | } | ||
| 38 | |||
| 39 | impl<'d, PIO: pio::Instance, const SM: usize> Ds18b20<'d, PIO, SM> { | ||
| 40 | /// Create a new instance the driver | ||
| 41 | pub fn new(common: &mut Common<'d, PIO>, mut sm: StateMachine<'d, PIO, SM>, pin: impl PioPin) -> Self { | ||
| 42 | let prg = pio_proc::pio_asm!( | ||
| 43 | r#" | ||
| 44 | .wrap_target | ||
| 45 | again: | ||
| 46 | pull block | ||
| 47 | mov x, osr | ||
| 48 | jmp !x, read | ||
| 49 | write: | ||
| 50 | set pindirs, 1 | ||
| 51 | set pins, 0 | ||
| 52 | loop1: | ||
| 53 | jmp x--,loop1 | ||
| 54 | set pindirs, 0 [31] | ||
| 55 | wait 1 pin 0 [31] | ||
| 56 | pull block | ||
| 57 | mov x, osr | ||
| 58 | bytes1: | ||
| 59 | pull block | ||
| 60 | set y, 7 | ||
| 61 | set pindirs, 1 | ||
| 62 | bit1: | ||
| 63 | set pins, 0 [1] | ||
| 64 | out pins,1 [31] | ||
| 65 | set pins, 1 [20] | ||
| 66 | jmp y--,bit1 | ||
| 67 | jmp x--,bytes1 | ||
| 68 | set pindirs, 0 [31] | ||
| 69 | jmp again | ||
| 70 | read: | ||
| 71 | pull block | ||
| 72 | mov x, osr | ||
| 73 | bytes2: | ||
| 74 | set y, 7 | ||
| 75 | bit2: | ||
| 76 | set pindirs, 1 | ||
| 77 | set pins, 0 [1] | ||
| 78 | set pindirs, 0 [5] | ||
| 79 | in pins,1 [10] | ||
| 80 | jmp y--,bit2 | ||
| 81 | jmp x--,bytes2 | ||
| 82 | .wrap | ||
| 83 | "#, | ||
| 84 | ); | ||
| 85 | |||
| 86 | let pin = common.make_pio_pin(pin); | ||
| 87 | let mut cfg = Config::default(); | ||
| 88 | cfg.use_program(&common.load_program(&prg.program), &[]); | ||
| 89 | cfg.set_out_pins(&[&pin]); | ||
| 90 | cfg.set_in_pins(&[&pin]); | ||
| 91 | cfg.set_set_pins(&[&pin]); | ||
| 92 | cfg.shift_in = ShiftConfig { | ||
| 93 | auto_fill: true, | ||
| 94 | direction: ShiftDirection::Right, | ||
| 95 | threshold: 8, | ||
| 96 | }; | ||
| 97 | cfg.clock_divider = 255_u8.into(); | ||
| 98 | sm.set_config(&cfg); | ||
| 99 | sm.set_enable(true); | ||
| 100 | Self { sm } | ||
| 101 | } | ||
| 102 | |||
| 103 | /// Write bytes over the wire | ||
| 104 | async fn write_bytes(&mut self, bytes: &[u8]) { | ||
| 105 | self.sm.tx().wait_push(250).await; | ||
| 106 | self.sm.tx().wait_push(bytes.len() as u32 - 1).await; | ||
| 107 | for b in bytes { | ||
| 108 | self.sm.tx().wait_push(*b as u32).await; | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | /// Read bytes from the wire | ||
| 113 | async fn read_bytes(&mut self, bytes: &mut [u8]) { | ||
| 114 | self.sm.tx().wait_push(0).await; | ||
| 115 | self.sm.tx().wait_push(bytes.len() as u32 - 1).await; | ||
| 116 | for b in bytes.iter_mut() { | ||
| 117 | *b = (self.sm.rx().wait_pull().await >> 24) as u8; | ||
| 118 | } | ||
| 119 | } | ||
| 120 | |||
| 121 | /// Calculate CRC8 of the data | ||
| 122 | fn crc8(data: &[u8]) -> u8 { | ||
| 123 | let mut temp; | ||
| 124 | let mut data_byte; | ||
| 125 | let mut crc = 0; | ||
| 126 | for b in data { | ||
| 127 | data_byte = *b; | ||
| 128 | for _ in 0..8 { | ||
| 129 | temp = (crc ^ data_byte) & 0x01; | ||
| 130 | crc >>= 1; | ||
| 131 | if temp != 0 { | ||
| 132 | crc ^= 0x8C; | ||
| 133 | } | ||
| 134 | data_byte >>= 1; | ||
| 135 | } | ||
| 136 | } | ||
| 137 | crc | ||
| 138 | } | ||
| 139 | |||
| 140 | /// Start a new measurement. Allow at least 1000ms before getting `temperature`. | ||
| 141 | pub async fn start(&mut self) { | ||
| 142 | self.write_bytes(&[0xCC, 0x44]).await; | ||
| 143 | } | ||
| 144 | |||
| 145 | /// Read the temperature. Ensure >1000ms has passed since `start` before calling this. | ||
| 146 | pub async fn temperature(&mut self) -> Result<f32, ()> { | ||
| 147 | self.write_bytes(&[0xCC, 0xBE]).await; | ||
| 148 | let mut data = [0; 9]; | ||
| 149 | self.read_bytes(&mut data).await; | ||
| 150 | match Self::crc8(&data) == 0 { | ||
| 151 | true => Ok(((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.), | ||
| 152 | false => Err(()), | ||
| 153 | } | ||
| 154 | } | ||
| 155 | } | ||
diff --git a/examples/rp/src/bin/pio_pwm.rs b/examples/rp/src/bin/pio_pwm.rs new file mode 100644 index 000000000..23d63d435 --- /dev/null +++ b/examples/rp/src/bin/pio_pwm.rs | |||
| @@ -0,0 +1,118 @@ | |||
| 1 | //! This example shows how to create a pwm using the PIO module in the RP2040 chip. | ||
| 2 | |||
| 3 | #![no_std] | ||
| 4 | #![no_main] | ||
| 5 | use core::time::Duration; | ||
| 6 | |||
| 7 | use embassy_executor::Spawner; | ||
| 8 | use embassy_rp::gpio::Level; | ||
| 9 | use embassy_rp::peripherals::PIO0; | ||
| 10 | use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine}; | ||
| 11 | use embassy_rp::{bind_interrupts, clocks}; | ||
| 12 | use embassy_time::Timer; | ||
| 13 | use pio::InstructionOperands; | ||
| 14 | use {defmt_rtt as _, panic_probe as _}; | ||
| 15 | |||
| 16 | const REFRESH_INTERVAL: u64 = 20000; | ||
| 17 | |||
| 18 | bind_interrupts!(struct Irqs { | ||
| 19 | PIO0_IRQ_0 => InterruptHandler<PIO0>; | ||
| 20 | }); | ||
| 21 | |||
| 22 | pub fn to_pio_cycles(duration: Duration) -> u32 { | ||
| 23 | (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow | ||
| 24 | } | ||
| 25 | |||
| 26 | pub struct PwmPio<'d, T: Instance, const SM: usize> { | ||
| 27 | sm: StateMachine<'d, T, SM>, | ||
| 28 | } | ||
| 29 | |||
| 30 | impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> { | ||
| 31 | pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> Self { | ||
| 32 | let prg = pio_proc::pio_asm!( | ||
| 33 | ".side_set 1 opt" | ||
| 34 | "pull noblock side 0" | ||
| 35 | "mov x, osr" | ||
| 36 | "mov y, isr" | ||
| 37 | "countloop:" | ||
| 38 | "jmp x!=y noset" | ||
| 39 | "jmp skip side 1" | ||
| 40 | "noset:" | ||
| 41 | "nop" | ||
| 42 | "skip:" | ||
| 43 | "jmp y-- countloop" | ||
| 44 | ); | ||
| 45 | |||
| 46 | pio.load_program(&prg.program); | ||
| 47 | let pin = pio.make_pio_pin(pin); | ||
| 48 | sm.set_pins(Level::High, &[&pin]); | ||
| 49 | sm.set_pin_dirs(Direction::Out, &[&pin]); | ||
| 50 | |||
| 51 | let mut cfg = Config::default(); | ||
| 52 | cfg.use_program(&pio.load_program(&prg.program), &[&pin]); | ||
| 53 | |||
| 54 | sm.set_config(&cfg); | ||
| 55 | |||
| 56 | Self { sm } | ||
| 57 | } | ||
| 58 | |||
| 59 | pub fn start(&mut self) { | ||
| 60 | self.sm.set_enable(true); | ||
| 61 | } | ||
| 62 | |||
| 63 | pub fn stop(&mut self) { | ||
| 64 | self.sm.set_enable(false); | ||
| 65 | } | ||
| 66 | |||
| 67 | pub fn set_period(&mut self, duration: Duration) { | ||
| 68 | let is_enabled = self.sm.is_enabled(); | ||
| 69 | while !self.sm.tx().empty() {} // Make sure that the queue is empty | ||
| 70 | self.sm.set_enable(false); | ||
| 71 | self.sm.tx().push(to_pio_cycles(duration)); | ||
| 72 | unsafe { | ||
| 73 | self.sm.exec_instr( | ||
| 74 | InstructionOperands::PULL { | ||
| 75 | if_empty: false, | ||
| 76 | block: false, | ||
| 77 | } | ||
| 78 | .encode(), | ||
| 79 | ); | ||
| 80 | self.sm.exec_instr( | ||
| 81 | InstructionOperands::OUT { | ||
| 82 | destination: ::pio::OutDestination::ISR, | ||
| 83 | bit_count: 32, | ||
| 84 | } | ||
| 85 | .encode(), | ||
| 86 | ); | ||
| 87 | }; | ||
| 88 | if is_enabled { | ||
| 89 | self.sm.set_enable(true) // Enable if previously enabled | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | pub fn set_level(&mut self, level: u32) { | ||
| 94 | self.sm.tx().push(level); | ||
| 95 | } | ||
| 96 | |||
| 97 | pub fn write(&mut self, duration: Duration) { | ||
| 98 | self.set_level(to_pio_cycles(duration)); | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | #[embassy_executor::main] | ||
| 103 | async fn main(_spawner: Spawner) { | ||
| 104 | let p = embassy_rp::init(Default::default()); | ||
| 105 | let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); | ||
| 106 | |||
| 107 | // Note that PIN_25 is the led pin on the Pico | ||
| 108 | let mut pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_25); | ||
| 109 | pwm_pio.set_period(Duration::from_micros(REFRESH_INTERVAL)); | ||
| 110 | pwm_pio.start(); | ||
| 111 | |||
| 112 | let mut duration = 0; | ||
| 113 | loop { | ||
| 114 | duration = (duration + 1) % 1000; | ||
| 115 | pwm_pio.write(Duration::from_micros(duration)); | ||
| 116 | Timer::after_millis(1).await; | ||
| 117 | } | ||
| 118 | } | ||
diff --git a/examples/rp/src/bin/pio_servo.rs b/examples/rp/src/bin/pio_servo.rs new file mode 100644 index 000000000..a79540479 --- /dev/null +++ b/examples/rp/src/bin/pio_servo.rs | |||
| @@ -0,0 +1,208 @@ | |||
| 1 | //! This example shows how to create a pwm using the PIO module in the RP2040 chip. | ||
| 2 | |||
| 3 | #![no_std] | ||
| 4 | #![no_main] | ||
| 5 | use core::time::Duration; | ||
| 6 | |||
| 7 | use embassy_executor::Spawner; | ||
| 8 | use embassy_rp::gpio::Level; | ||
| 9 | use embassy_rp::peripherals::PIO0; | ||
| 10 | use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine}; | ||
| 11 | use embassy_rp::{bind_interrupts, clocks}; | ||
| 12 | use embassy_time::Timer; | ||
| 13 | use pio::InstructionOperands; | ||
| 14 | use {defmt_rtt as _, panic_probe as _}; | ||
| 15 | |||
| 16 | const DEFAULT_MIN_PULSE_WIDTH: u64 = 1000; // uncalibrated default, the shortest duty cycle sent to a servo | ||
| 17 | const DEFAULT_MAX_PULSE_WIDTH: u64 = 2000; // uncalibrated default, the longest duty cycle sent to a servo | ||
| 18 | const DEFAULT_MAX_DEGREE_ROTATION: u64 = 160; // 160 degrees is typical | ||
| 19 | const REFRESH_INTERVAL: u64 = 20000; // The period of each cycle | ||
| 20 | |||
| 21 | bind_interrupts!(struct Irqs { | ||
| 22 | PIO0_IRQ_0 => InterruptHandler<PIO0>; | ||
| 23 | }); | ||
| 24 | |||
| 25 | pub fn to_pio_cycles(duration: Duration) -> u32 { | ||
| 26 | (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow | ||
| 27 | } | ||
| 28 | |||
| 29 | pub struct PwmPio<'d, T: Instance, const SM: usize> { | ||
| 30 | sm: StateMachine<'d, T, SM>, | ||
| 31 | } | ||
| 32 | |||
| 33 | impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> { | ||
| 34 | pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> Self { | ||
| 35 | let prg = pio_proc::pio_asm!( | ||
| 36 | ".side_set 1 opt" | ||
| 37 | "pull noblock side 0" | ||
| 38 | "mov x, osr" | ||
| 39 | "mov y, isr" | ||
| 40 | "countloop:" | ||
| 41 | "jmp x!=y noset" | ||
| 42 | "jmp skip side 1" | ||
| 43 | "noset:" | ||
| 44 | "nop" | ||
| 45 | "skip:" | ||
| 46 | "jmp y-- countloop" | ||
| 47 | ); | ||
| 48 | |||
| 49 | pio.load_program(&prg.program); | ||
| 50 | let pin = pio.make_pio_pin(pin); | ||
| 51 | sm.set_pins(Level::High, &[&pin]); | ||
| 52 | sm.set_pin_dirs(Direction::Out, &[&pin]); | ||
| 53 | |||
| 54 | let mut cfg = Config::default(); | ||
| 55 | cfg.use_program(&pio.load_program(&prg.program), &[&pin]); | ||
| 56 | |||
| 57 | sm.set_config(&cfg); | ||
| 58 | |||
| 59 | Self { sm } | ||
| 60 | } | ||
| 61 | |||
| 62 | pub fn start(&mut self) { | ||
| 63 | self.sm.set_enable(true); | ||
| 64 | } | ||
| 65 | |||
| 66 | pub fn stop(&mut self) { | ||
| 67 | self.sm.set_enable(false); | ||
| 68 | } | ||
| 69 | |||
| 70 | pub fn set_period(&mut self, duration: Duration) { | ||
| 71 | let is_enabled = self.sm.is_enabled(); | ||
| 72 | while !self.sm.tx().empty() {} // Make sure that the queue is empty | ||
| 73 | self.sm.set_enable(false); | ||
| 74 | self.sm.tx().push(to_pio_cycles(duration)); | ||
| 75 | unsafe { | ||
| 76 | self.sm.exec_instr( | ||
| 77 | InstructionOperands::PULL { | ||
| 78 | if_empty: false, | ||
| 79 | block: false, | ||
| 80 | } | ||
| 81 | .encode(), | ||
| 82 | ); | ||
| 83 | self.sm.exec_instr( | ||
| 84 | InstructionOperands::OUT { | ||
| 85 | destination: ::pio::OutDestination::ISR, | ||
| 86 | bit_count: 32, | ||
| 87 | } | ||
| 88 | .encode(), | ||
| 89 | ); | ||
| 90 | }; | ||
| 91 | if is_enabled { | ||
| 92 | self.sm.set_enable(true) // Enable if previously enabled | ||
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 96 | pub fn set_level(&mut self, level: u32) { | ||
| 97 | self.sm.tx().push(level); | ||
| 98 | } | ||
| 99 | |||
| 100 | pub fn write(&mut self, duration: Duration) { | ||
| 101 | self.set_level(to_pio_cycles(duration)); | ||
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 105 | pub struct ServoBuilder<'d, T: Instance, const SM: usize> { | ||
| 106 | pwm: PwmPio<'d, T, SM>, | ||
| 107 | period: Duration, | ||
| 108 | min_pulse_width: Duration, | ||
| 109 | max_pulse_width: Duration, | ||
| 110 | max_degree_rotation: u64, | ||
| 111 | } | ||
| 112 | |||
| 113 | impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> { | ||
| 114 | pub fn new(pwm: PwmPio<'d, T, SM>) -> Self { | ||
| 115 | Self { | ||
| 116 | pwm, | ||
| 117 | period: Duration::from_micros(REFRESH_INTERVAL), | ||
| 118 | min_pulse_width: Duration::from_micros(DEFAULT_MIN_PULSE_WIDTH), | ||
| 119 | max_pulse_width: Duration::from_micros(DEFAULT_MAX_PULSE_WIDTH), | ||
| 120 | max_degree_rotation: DEFAULT_MAX_DEGREE_ROTATION, | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | pub fn set_period(mut self, duration: Duration) -> Self { | ||
| 125 | self.period = duration; | ||
| 126 | self | ||
| 127 | } | ||
| 128 | |||
| 129 | pub fn set_min_pulse_width(mut self, duration: Duration) -> Self { | ||
| 130 | self.min_pulse_width = duration; | ||
| 131 | self | ||
| 132 | } | ||
| 133 | |||
| 134 | pub fn set_max_pulse_width(mut self, duration: Duration) -> Self { | ||
| 135 | self.max_pulse_width = duration; | ||
| 136 | self | ||
| 137 | } | ||
| 138 | |||
| 139 | pub fn set_max_degree_rotation(mut self, degree: u64) -> Self { | ||
| 140 | self.max_degree_rotation = degree; | ||
| 141 | self | ||
| 142 | } | ||
| 143 | |||
| 144 | pub fn build(mut self) -> Servo<'d, T, SM> { | ||
| 145 | self.pwm.set_period(self.period); | ||
| 146 | Servo { | ||
| 147 | pwm: self.pwm, | ||
| 148 | min_pulse_width: self.min_pulse_width, | ||
| 149 | max_pulse_width: self.max_pulse_width, | ||
| 150 | max_degree_rotation: self.max_degree_rotation, | ||
| 151 | } | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | pub struct Servo<'d, T: Instance, const SM: usize> { | ||
| 156 | pwm: PwmPio<'d, T, SM>, | ||
| 157 | min_pulse_width: Duration, | ||
| 158 | max_pulse_width: Duration, | ||
| 159 | max_degree_rotation: u64, | ||
| 160 | } | ||
| 161 | |||
| 162 | impl<'d, T: Instance, const SM: usize> Servo<'d, T, SM> { | ||
| 163 | pub fn start(&mut self) { | ||
| 164 | self.pwm.start(); | ||
| 165 | } | ||
| 166 | |||
| 167 | pub fn stop(&mut self) { | ||
| 168 | self.pwm.stop(); | ||
| 169 | } | ||
| 170 | |||
| 171 | pub fn write_time(&mut self, duration: Duration) { | ||
| 172 | self.pwm.write(duration); | ||
| 173 | } | ||
| 174 | |||
| 175 | pub fn rotate(&mut self, degree: u64) { | ||
| 176 | let degree_per_nano_second = (self.max_pulse_width.as_nanos() as u64 - self.min_pulse_width.as_nanos() as u64) | ||
| 177 | / self.max_degree_rotation; | ||
| 178 | let mut duration = | ||
| 179 | Duration::from_nanos(degree * degree_per_nano_second + self.min_pulse_width.as_nanos() as u64); | ||
| 180 | if self.max_pulse_width < duration { | ||
| 181 | duration = self.max_pulse_width; | ||
| 182 | } | ||
| 183 | |||
| 184 | self.pwm.write(duration); | ||
| 185 | } | ||
| 186 | } | ||
| 187 | |||
| 188 | #[embassy_executor::main] | ||
| 189 | async fn main(_spawner: Spawner) { | ||
| 190 | let p = embassy_rp::init(Default::default()); | ||
| 191 | let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); | ||
| 192 | |||
| 193 | let pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_1); | ||
| 194 | let mut servo = ServoBuilder::new(pwm_pio) | ||
| 195 | .set_max_degree_rotation(120) // Example of adjusting values for MG996R servo | ||
| 196 | .set_min_pulse_width(Duration::from_micros(350)) // This value was detemined by a rough experiment. | ||
| 197 | .set_max_pulse_width(Duration::from_micros(2600)) // Along with this value. | ||
| 198 | .build(); | ||
| 199 | |||
| 200 | servo.start(); | ||
| 201 | |||
| 202 | let mut degree = 0; | ||
| 203 | loop { | ||
| 204 | degree = (degree + 1) % 120; | ||
| 205 | servo.rotate(degree); | ||
| 206 | Timer::after_millis(50).await; | ||
| 207 | } | ||
| 208 | } | ||
diff --git a/examples/rp/src/bin/pio_stepper.rs b/examples/rp/src/bin/pio_stepper.rs index ab9ecf623..4952f4fbd 100644 --- a/examples/rp/src/bin/pio_stepper.rs +++ b/examples/rp/src/bin/pio_stepper.rs | |||
| @@ -69,7 +69,7 @@ impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { | |||
| 69 | let clock_divider: FixedU32<U8> = (125_000_000 / (freq * 136)).to_fixed(); | 69 | let clock_divider: FixedU32<U8> = (125_000_000 / (freq * 136)).to_fixed(); |
| 70 | assert!(clock_divider <= 65536, "clkdiv must be <= 65536"); | 70 | assert!(clock_divider <= 65536, "clkdiv must be <= 65536"); |
| 71 | assert!(clock_divider >= 1, "clkdiv must be >= 1"); | 71 | assert!(clock_divider >= 1, "clkdiv must be >= 1"); |
| 72 | T::PIO.sm(SM).clkdiv().write(|w| w.0 = clock_divider.to_bits() << 8); | 72 | self.sm.set_clock_divider(clock_divider); |
| 73 | self.sm.clkdiv_restart(); | 73 | self.sm.clkdiv_restart(); |
| 74 | } | 74 | } |
| 75 | 75 | ||
diff --git a/examples/rp/src/bin/pio_uart.rs b/examples/rp/src/bin/pio_uart.rs index a07f1c180..53b696309 100644 --- a/examples/rp/src/bin/pio_uart.rs +++ b/examples/rp/src/bin/pio_uart.rs | |||
| @@ -60,7 +60,6 @@ async fn main(_spawner: Spawner) { | |||
| 60 | 60 | ||
| 61 | // Create embassy-usb DeviceBuilder using the driver and config. | 61 | // Create embassy-usb DeviceBuilder using the driver and config. |
| 62 | // It needs some buffers for building the descriptors. | 62 | // It needs some buffers for building the descriptors. |
| 63 | let mut device_descriptor = [0; 256]; | ||
| 64 | let mut config_descriptor = [0; 256]; | 63 | let mut config_descriptor = [0; 256]; |
| 65 | let mut bos_descriptor = [0; 256]; | 64 | let mut bos_descriptor = [0; 256]; |
| 66 | let mut control_buf = [0; 64]; | 65 | let mut control_buf = [0; 64]; |
| @@ -70,7 +69,6 @@ async fn main(_spawner: Spawner) { | |||
| 70 | let mut builder = Builder::new( | 69 | let mut builder = Builder::new( |
| 71 | driver, | 70 | driver, |
| 72 | config, | 71 | config, |
| 73 | &mut device_descriptor, | ||
| 74 | &mut config_descriptor, | 72 | &mut config_descriptor, |
| 75 | &mut bos_descriptor, | 73 | &mut bos_descriptor, |
| 76 | &mut [], // no msos descriptors | 74 | &mut [], // no msos descriptors |
diff --git a/examples/rp/src/bin/pio_ws2812.rs b/examples/rp/src/bin/pio_ws2812.rs index 9a97cb8a7..ac145933c 100644 --- a/examples/rp/src/bin/pio_ws2812.rs +++ b/examples/rp/src/bin/pio_ws2812.rs | |||
| @@ -12,7 +12,7 @@ use embassy_rp::pio::{ | |||
| 12 | Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, | 12 | Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, |
| 13 | }; | 13 | }; |
| 14 | use embassy_rp::{bind_interrupts, clocks, into_ref, Peripheral, PeripheralRef}; | 14 | use embassy_rp::{bind_interrupts, clocks, into_ref, Peripheral, PeripheralRef}; |
| 15 | use embassy_time::Timer; | 15 | use embassy_time::{Duration, Ticker, Timer}; |
| 16 | use fixed::types::U24F8; | 16 | use fixed::types::U24F8; |
| 17 | use fixed_macro::fixed; | 17 | use fixed_macro::fixed; |
| 18 | use smart_leds::RGB8; | 18 | use smart_leds::RGB8; |
| @@ -107,6 +107,8 @@ impl<'d, P: Instance, const S: usize, const N: usize> Ws2812<'d, P, S, N> { | |||
| 107 | 107 | ||
| 108 | // DMA transfer | 108 | // DMA transfer |
| 109 | self.sm.tx().dma_push(self.dma.reborrow(), &words).await; | 109 | self.sm.tx().dma_push(self.dma.reborrow(), &words).await; |
| 110 | |||
| 111 | Timer::after_micros(55).await; | ||
| 110 | } | 112 | } |
| 111 | } | 113 | } |
| 112 | 114 | ||
| @@ -143,6 +145,7 @@ async fn main(_spawner: Spawner) { | |||
| 143 | let mut ws2812 = Ws2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16); | 145 | let mut ws2812 = Ws2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16); |
| 144 | 146 | ||
| 145 | // Loop forever making RGB values and pushing them out to the WS2812. | 147 | // Loop forever making RGB values and pushing them out to the WS2812. |
| 148 | let mut ticker = Ticker::every(Duration::from_millis(10)); | ||
| 146 | loop { | 149 | loop { |
| 147 | for j in 0..(256 * 5) { | 150 | for j in 0..(256 * 5) { |
| 148 | debug!("New Colors:"); | 151 | debug!("New Colors:"); |
| @@ -152,7 +155,7 @@ async fn main(_spawner: Spawner) { | |||
| 152 | } | 155 | } |
| 153 | ws2812.write(&data).await; | 156 | ws2812.write(&data).await; |
| 154 | 157 | ||
| 155 | Timer::after_millis(10).await; | 158 | ticker.next().await; |
| 156 | } | 159 | } |
| 157 | } | 160 | } |
| 158 | } | 161 | } |
diff --git a/examples/rp/src/bin/pwm.rs b/examples/rp/src/bin/pwm.rs index 4fb62546d..26e233260 100644 --- a/examples/rp/src/bin/pwm.rs +++ b/examples/rp/src/bin/pwm.rs | |||
| @@ -18,7 +18,7 @@ async fn main(_spawner: Spawner) { | |||
| 18 | let mut c: Config = Default::default(); | 18 | let mut c: Config = Default::default(); |
| 19 | c.top = 0x8000; | 19 | c.top = 0x8000; |
| 20 | c.compare_b = 8; | 20 | c.compare_b = 8; |
| 21 | let mut pwm = Pwm::new_output_b(p.PWM_CH4, p.PIN_25, c.clone()); | 21 | let mut pwm = Pwm::new_output_b(p.PWM_SLICE4, p.PIN_25, c.clone()); |
| 22 | 22 | ||
| 23 | loop { | 23 | loop { |
| 24 | info!("current LED duty cycle: {}/32768", c.compare_b); | 24 | info!("current LED duty cycle: {}/32768", c.compare_b); |
diff --git a/examples/rp/src/bin/pwm_input.rs b/examples/rp/src/bin/pwm_input.rs index e7bcbfbd4..bf454a936 100644 --- a/examples/rp/src/bin/pwm_input.rs +++ b/examples/rp/src/bin/pwm_input.rs | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | 5 | ||
| 6 | use defmt::*; | 6 | use defmt::*; |
| 7 | use embassy_executor::Spawner; | 7 | use embassy_executor::Spawner; |
| 8 | use embassy_rp::gpio::Pull; | ||
| 8 | use embassy_rp::pwm::{Config, InputMode, Pwm}; | 9 | use embassy_rp::pwm::{Config, InputMode, Pwm}; |
| 9 | use embassy_time::{Duration, Ticker}; | 10 | use embassy_time::{Duration, Ticker}; |
| 10 | use {defmt_rtt as _, panic_probe as _}; | 11 | use {defmt_rtt as _, panic_probe as _}; |
| @@ -14,7 +15,7 @@ async fn main(_spawner: Spawner) { | |||
| 14 | let p = embassy_rp::init(Default::default()); | 15 | let p = embassy_rp::init(Default::default()); |
| 15 | 16 | ||
| 16 | let cfg: Config = Default::default(); | 17 | let cfg: Config = Default::default(); |
| 17 | let pwm = Pwm::new_input(p.PWM_CH2, p.PIN_5, InputMode::RisingEdge, cfg); | 18 | let pwm = Pwm::new_input(p.PWM_SLICE2, p.PIN_5, Pull::None, InputMode::RisingEdge, cfg); |
| 18 | 19 | ||
| 19 | let mut ticker = Ticker::every(Duration::from_secs(1)); | 20 | let mut ticker = Ticker::every(Duration::from_secs(1)); |
| 20 | loop { | 21 | loop { |
diff --git a/examples/rp/src/bin/shared_bus.rs b/examples/rp/src/bin/shared_bus.rs new file mode 100644 index 000000000..c6cb5d64c --- /dev/null +++ b/examples/rp/src/bin/shared_bus.rs | |||
| @@ -0,0 +1,115 @@ | |||
| 1 | //! This example shows how to share (async) I2C and SPI buses between multiple devices. | ||
| 2 | |||
| 3 | #![no_std] | ||
| 4 | #![no_main] | ||
| 5 | |||
| 6 | use defmt::*; | ||
| 7 | use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice; | ||
| 8 | use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice; | ||
| 9 | use embassy_executor::Spawner; | ||
| 10 | use embassy_rp::bind_interrupts; | ||
| 11 | use embassy_rp::gpio::{AnyPin, Level, Output}; | ||
| 12 | use embassy_rp::i2c::{self, I2c, InterruptHandler}; | ||
| 13 | use embassy_rp::peripherals::{I2C1, SPI1}; | ||
| 14 | use embassy_rp::spi::{self, Spi}; | ||
| 15 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 16 | use embassy_sync::mutex::Mutex; | ||
| 17 | use embassy_time::Timer; | ||
| 18 | use static_cell::StaticCell; | ||
| 19 | use {defmt_rtt as _, panic_probe as _}; | ||
| 20 | |||
| 21 | type Spi1Bus = Mutex<NoopRawMutex, Spi<'static, SPI1, spi::Async>>; | ||
| 22 | type I2c1Bus = Mutex<NoopRawMutex, I2c<'static, I2C1, i2c::Async>>; | ||
| 23 | |||
| 24 | bind_interrupts!(struct Irqs { | ||
| 25 | I2C1_IRQ => InterruptHandler<I2C1>; | ||
| 26 | }); | ||
| 27 | |||
| 28 | #[embassy_executor::main] | ||
| 29 | async fn main(spawner: Spawner) { | ||
| 30 | let p = embassy_rp::init(Default::default()); | ||
| 31 | info!("Here we go!"); | ||
| 32 | |||
| 33 | // Shared I2C bus | ||
| 34 | let i2c = I2c::new_async(p.I2C1, p.PIN_15, p.PIN_14, Irqs, i2c::Config::default()); | ||
| 35 | static I2C_BUS: StaticCell<I2c1Bus> = StaticCell::new(); | ||
| 36 | let i2c_bus = I2C_BUS.init(Mutex::new(i2c)); | ||
| 37 | |||
| 38 | spawner.must_spawn(i2c_task_a(i2c_bus)); | ||
| 39 | spawner.must_spawn(i2c_task_b(i2c_bus)); | ||
| 40 | |||
| 41 | // Shared SPI bus | ||
| 42 | let spi_cfg = spi::Config::default(); | ||
| 43 | let spi = Spi::new(p.SPI1, p.PIN_10, p.PIN_11, p.PIN_12, p.DMA_CH0, p.DMA_CH1, spi_cfg); | ||
| 44 | static SPI_BUS: StaticCell<Spi1Bus> = StaticCell::new(); | ||
| 45 | let spi_bus = SPI_BUS.init(Mutex::new(spi)); | ||
| 46 | |||
| 47 | // Chip select pins for the SPI devices | ||
| 48 | let cs_a = Output::new(AnyPin::from(p.PIN_0), Level::High); | ||
| 49 | let cs_b = Output::new(AnyPin::from(p.PIN_1), Level::High); | ||
| 50 | |||
| 51 | spawner.must_spawn(spi_task_a(spi_bus, cs_a)); | ||
| 52 | spawner.must_spawn(spi_task_b(spi_bus, cs_b)); | ||
| 53 | } | ||
| 54 | |||
| 55 | #[embassy_executor::task] | ||
| 56 | async fn i2c_task_a(i2c_bus: &'static I2c1Bus) { | ||
| 57 | let i2c_dev = I2cDevice::new(i2c_bus); | ||
| 58 | let _sensor = DummyI2cDeviceDriver::new(i2c_dev, 0xC0); | ||
| 59 | loop { | ||
| 60 | info!("i2c task A"); | ||
| 61 | Timer::after_secs(1).await; | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | #[embassy_executor::task] | ||
| 66 | async fn i2c_task_b(i2c_bus: &'static I2c1Bus) { | ||
| 67 | let i2c_dev = I2cDevice::new(i2c_bus); | ||
| 68 | let _sensor = DummyI2cDeviceDriver::new(i2c_dev, 0xDE); | ||
| 69 | loop { | ||
| 70 | info!("i2c task B"); | ||
| 71 | Timer::after_secs(1).await; | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | #[embassy_executor::task] | ||
| 76 | async fn spi_task_a(spi_bus: &'static Spi1Bus, cs: Output<'static>) { | ||
| 77 | let spi_dev = SpiDevice::new(spi_bus, cs); | ||
| 78 | let _sensor = DummySpiDeviceDriver::new(spi_dev); | ||
| 79 | loop { | ||
| 80 | info!("spi task A"); | ||
| 81 | Timer::after_secs(1).await; | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | #[embassy_executor::task] | ||
| 86 | async fn spi_task_b(spi_bus: &'static Spi1Bus, cs: Output<'static>) { | ||
| 87 | let spi_dev = SpiDevice::new(spi_bus, cs); | ||
| 88 | let _sensor = DummySpiDeviceDriver::new(spi_dev); | ||
| 89 | loop { | ||
| 90 | info!("spi task B"); | ||
| 91 | Timer::after_secs(1).await; | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | // Dummy I2C device driver, using `embedded-hal-async` | ||
| 96 | struct DummyI2cDeviceDriver<I2C: embedded_hal_async::i2c::I2c> { | ||
| 97 | _i2c: I2C, | ||
| 98 | } | ||
| 99 | |||
| 100 | impl<I2C: embedded_hal_async::i2c::I2c> DummyI2cDeviceDriver<I2C> { | ||
| 101 | fn new(i2c_dev: I2C, _address: u8) -> Self { | ||
| 102 | Self { _i2c: i2c_dev } | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | // Dummy SPI device driver, using `embedded-hal-async` | ||
| 107 | struct DummySpiDeviceDriver<SPI: embedded_hal_async::spi::SpiDevice> { | ||
| 108 | _spi: SPI, | ||
| 109 | } | ||
| 110 | |||
| 111 | impl<SPI: embedded_hal_async::spi::SpiDevice> DummySpiDeviceDriver<SPI> { | ||
| 112 | fn new(spi_dev: SPI) -> Self { | ||
| 113 | Self { _spi: spi_dev } | ||
| 114 | } | ||
| 115 | } | ||
diff --git a/examples/rp/src/bin/sharing.rs b/examples/rp/src/bin/sharing.rs new file mode 100644 index 000000000..5416e20ce --- /dev/null +++ b/examples/rp/src/bin/sharing.rs | |||
| @@ -0,0 +1,150 @@ | |||
| 1 | //! This example shows some common strategies for sharing resources between tasks. | ||
| 2 | //! | ||
| 3 | //! We demonstrate five different ways of sharing, covering different use cases: | ||
| 4 | //! - Atomics: This method is used for simple values, such as bool and u8..u32 | ||
| 5 | //! - Blocking Mutex: This is used for sharing non-async things, using Cell/RefCell for interior mutability. | ||
| 6 | //! - Async Mutex: This is used for sharing async resources, where you need to hold the lock across await points. | ||
| 7 | //! The async Mutex has interior mutability built-in, so no RefCell is needed. | ||
| 8 | //! - Cell: For sharing Copy types between tasks running on the same executor. | ||
| 9 | //! - RefCell: When you want &mut access to a value shared between tasks running on the same executor. | ||
| 10 | //! | ||
| 11 | //! More information: https://embassy.dev/book/#_sharing_peripherals_between_tasks | ||
| 12 | |||
| 13 | #![no_std] | ||
| 14 | #![no_main] | ||
| 15 | |||
| 16 | use core::cell::{Cell, RefCell}; | ||
| 17 | use core::sync::atomic::{AtomicU32, Ordering}; | ||
| 18 | |||
| 19 | use cortex_m_rt::entry; | ||
| 20 | use defmt::info; | ||
| 21 | use embassy_executor::{Executor, InterruptExecutor}; | ||
| 22 | use embassy_rp::clocks::RoscRng; | ||
| 23 | use embassy_rp::interrupt::{InterruptExt, Priority}; | ||
| 24 | use embassy_rp::peripherals::UART0; | ||
| 25 | use embassy_rp::uart::{self, InterruptHandler, UartTx}; | ||
| 26 | use embassy_rp::{bind_interrupts, interrupt}; | ||
| 27 | use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; | ||
| 28 | use embassy_sync::{blocking_mutex, mutex}; | ||
| 29 | use embassy_time::{Duration, Ticker}; | ||
| 30 | use rand::RngCore; | ||
| 31 | use static_cell::{ConstStaticCell, StaticCell}; | ||
| 32 | use {defmt_rtt as _, panic_probe as _}; | ||
| 33 | |||
| 34 | type UartAsyncMutex = mutex::Mutex<CriticalSectionRawMutex, UartTx<'static, UART0, uart::Async>>; | ||
| 35 | |||
| 36 | struct MyType { | ||
| 37 | inner: u32, | ||
| 38 | } | ||
| 39 | |||
| 40 | static EXECUTOR_HI: InterruptExecutor = InterruptExecutor::new(); | ||
| 41 | static EXECUTOR_LOW: StaticCell<Executor> = StaticCell::new(); | ||
| 42 | |||
| 43 | // Use Atomics for simple values | ||
| 44 | static ATOMIC: AtomicU32 = AtomicU32::new(0); | ||
| 45 | |||
| 46 | // Use blocking Mutex with Cell/RefCell for sharing non-async things | ||
| 47 | static MUTEX_BLOCKING: blocking_mutex::Mutex<CriticalSectionRawMutex, RefCell<MyType>> = | ||
| 48 | blocking_mutex::Mutex::new(RefCell::new(MyType { inner: 0 })); | ||
| 49 | |||
| 50 | bind_interrupts!(struct Irqs { | ||
| 51 | UART0_IRQ => InterruptHandler<UART0>; | ||
| 52 | }); | ||
| 53 | |||
| 54 | #[interrupt] | ||
| 55 | unsafe fn SWI_IRQ_0() { | ||
| 56 | EXECUTOR_HI.on_interrupt() | ||
| 57 | } | ||
| 58 | |||
| 59 | #[entry] | ||
| 60 | fn main() -> ! { | ||
| 61 | let p = embassy_rp::init(Default::default()); | ||
| 62 | info!("Here we go!"); | ||
| 63 | |||
| 64 | let uart = UartTx::new(p.UART0, p.PIN_0, p.DMA_CH0, uart::Config::default()); | ||
| 65 | // Use the async Mutex for sharing async things (built-in interior mutability) | ||
| 66 | static UART: StaticCell<UartAsyncMutex> = StaticCell::new(); | ||
| 67 | let uart = UART.init(mutex::Mutex::new(uart)); | ||
| 68 | |||
| 69 | // High-priority executor: runs in interrupt mode | ||
| 70 | interrupt::SWI_IRQ_0.set_priority(Priority::P3); | ||
| 71 | let spawner = EXECUTOR_HI.start(interrupt::SWI_IRQ_0); | ||
| 72 | spawner.must_spawn(task_a(uart)); | ||
| 73 | |||
| 74 | // Low priority executor: runs in thread mode | ||
| 75 | let executor = EXECUTOR_LOW.init(Executor::new()); | ||
| 76 | executor.run(|spawner| { | ||
| 77 | // No Mutex needed when sharing between tasks running on the same executor | ||
| 78 | |||
| 79 | // Use Cell for Copy-types | ||
| 80 | static CELL: ConstStaticCell<Cell<[u8; 4]>> = ConstStaticCell::new(Cell::new([0; 4])); | ||
| 81 | let cell = CELL.take(); | ||
| 82 | |||
| 83 | // Use RefCell for &mut access | ||
| 84 | static REF_CELL: ConstStaticCell<RefCell<MyType>> = ConstStaticCell::new(RefCell::new(MyType { inner: 0 })); | ||
| 85 | let ref_cell = REF_CELL.take(); | ||
| 86 | |||
| 87 | spawner.must_spawn(task_b(uart, cell, ref_cell)); | ||
| 88 | spawner.must_spawn(task_c(cell, ref_cell)); | ||
| 89 | }); | ||
| 90 | } | ||
| 91 | |||
| 92 | #[embassy_executor::task] | ||
| 93 | async fn task_a(uart: &'static UartAsyncMutex) { | ||
| 94 | let mut ticker = Ticker::every(Duration::from_secs(1)); | ||
| 95 | loop { | ||
| 96 | let random = RoscRng.next_u32(); | ||
| 97 | |||
| 98 | { | ||
| 99 | let mut uart = uart.lock().await; | ||
| 100 | uart.write(b"task a").await.unwrap(); | ||
| 101 | // The uart lock is released when it goes out of scope | ||
| 102 | } | ||
| 103 | |||
| 104 | ATOMIC.store(random, Ordering::Relaxed); | ||
| 105 | |||
| 106 | MUTEX_BLOCKING.lock(|x| x.borrow_mut().inner = random); | ||
| 107 | |||
| 108 | ticker.next().await; | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | #[embassy_executor::task] | ||
| 113 | async fn task_b(uart: &'static UartAsyncMutex, cell: &'static Cell<[u8; 4]>, ref_cell: &'static RefCell<MyType>) { | ||
| 114 | let mut ticker = Ticker::every(Duration::from_secs(1)); | ||
| 115 | loop { | ||
| 116 | let random = RoscRng.next_u32(); | ||
| 117 | |||
| 118 | uart.lock().await.write(b"task b").await.unwrap(); | ||
| 119 | |||
| 120 | cell.set(random.to_be_bytes()); | ||
| 121 | |||
| 122 | ref_cell.borrow_mut().inner = random; | ||
| 123 | |||
| 124 | ticker.next().await; | ||
| 125 | } | ||
| 126 | } | ||
| 127 | |||
| 128 | #[embassy_executor::task] | ||
| 129 | async fn task_c(cell: &'static Cell<[u8; 4]>, ref_cell: &'static RefCell<MyType>) { | ||
| 130 | let mut ticker = Ticker::every(Duration::from_secs(1)); | ||
| 131 | loop { | ||
| 132 | info!("======================="); | ||
| 133 | |||
| 134 | let atomic_val = ATOMIC.load(Ordering::Relaxed); | ||
| 135 | info!("atomic: {}", atomic_val); | ||
| 136 | |||
| 137 | MUTEX_BLOCKING.lock(|x| { | ||
| 138 | let val = x.borrow().inner; | ||
| 139 | info!("blocking mutex: {}", val); | ||
| 140 | }); | ||
| 141 | |||
| 142 | let cell_val = cell.get(); | ||
| 143 | info!("cell: {:?}", cell_val); | ||
| 144 | |||
| 145 | let ref_cell_val = ref_cell.borrow().inner; | ||
| 146 | info!("ref_cell: {:?}", ref_cell_val); | ||
| 147 | |||
| 148 | ticker.next().await; | ||
| 149 | } | ||
| 150 | } | ||
diff --git a/examples/rp/src/bin/spi_sdmmc.rs b/examples/rp/src/bin/spi_sdmmc.rs new file mode 100644 index 000000000..4cbc82f7b --- /dev/null +++ b/examples/rp/src/bin/spi_sdmmc.rs | |||
| @@ -0,0 +1,83 @@ | |||
| 1 | //! This example shows how to use `embedded-sdmmc` with the RP2040 chip, over SPI. | ||
| 2 | //! | ||
| 3 | //! The example will attempt to read a file `MY_FILE.TXT` from the root directory | ||
| 4 | //! of the SD card and print its contents. | ||
| 5 | |||
| 6 | #![no_std] | ||
| 7 | #![no_main] | ||
| 8 | |||
| 9 | use defmt::*; | ||
| 10 | use embassy_embedded_hal::SetConfig; | ||
| 11 | use embassy_executor::Spawner; | ||
| 12 | use embassy_rp::spi::Spi; | ||
| 13 | use embassy_rp::{gpio, spi}; | ||
| 14 | use embedded_hal_bus::spi::ExclusiveDevice; | ||
| 15 | use embedded_sdmmc::sdcard::{DummyCsPin, SdCard}; | ||
| 16 | use gpio::{Level, Output}; | ||
| 17 | use {defmt_rtt as _, panic_probe as _}; | ||
| 18 | |||
| 19 | struct DummyTimesource(); | ||
| 20 | |||
| 21 | impl embedded_sdmmc::TimeSource for DummyTimesource { | ||
| 22 | fn get_timestamp(&self) -> embedded_sdmmc::Timestamp { | ||
| 23 | embedded_sdmmc::Timestamp { | ||
| 24 | year_since_1970: 0, | ||
| 25 | zero_indexed_month: 0, | ||
| 26 | zero_indexed_day: 0, | ||
| 27 | hours: 0, | ||
| 28 | minutes: 0, | ||
| 29 | seconds: 0, | ||
| 30 | } | ||
| 31 | } | ||
| 32 | } | ||
| 33 | |||
| 34 | #[embassy_executor::main] | ||
| 35 | async fn main(_spawner: Spawner) { | ||
| 36 | embassy_rp::pac::SIO.spinlock(31).write_value(1); | ||
| 37 | let p = embassy_rp::init(Default::default()); | ||
| 38 | |||
| 39 | // SPI clock needs to be running at <= 400kHz during initialization | ||
| 40 | let mut config = spi::Config::default(); | ||
| 41 | config.frequency = 400_000; | ||
| 42 | let spi = Spi::new_blocking(p.SPI1, p.PIN_10, p.PIN_11, p.PIN_12, config); | ||
| 43 | // Use a dummy cs pin here, for embedded-hal SpiDevice compatibility reasons | ||
| 44 | let spi_dev = ExclusiveDevice::new_no_delay(spi, DummyCsPin); | ||
| 45 | // Real cs pin | ||
| 46 | let cs = Output::new(p.PIN_16, Level::High); | ||
| 47 | |||
| 48 | let sdcard = SdCard::new(spi_dev, cs, embassy_time::Delay); | ||
| 49 | info!("Card size is {} bytes", sdcard.num_bytes().unwrap()); | ||
| 50 | |||
| 51 | // Now that the card is initialized, the SPI clock can go faster | ||
| 52 | let mut config = spi::Config::default(); | ||
| 53 | config.frequency = 16_000_000; | ||
| 54 | sdcard.spi(|dev| dev.bus_mut().set_config(&config)).ok(); | ||
| 55 | |||
| 56 | // Now let's look for volumes (also known as partitions) on our block device. | ||
| 57 | // To do this we need a Volume Manager. It will take ownership of the block device. | ||
| 58 | let mut volume_mgr = embedded_sdmmc::VolumeManager::new(sdcard, DummyTimesource()); | ||
| 59 | |||
| 60 | // Try and access Volume 0 (i.e. the first partition). | ||
| 61 | // The volume object holds information about the filesystem on that volume. | ||
| 62 | let mut volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0)).unwrap(); | ||
| 63 | info!("Volume 0: {:?}", defmt::Debug2Format(&volume0)); | ||
| 64 | |||
| 65 | // Open the root directory (mutably borrows from the volume). | ||
| 66 | let mut root_dir = volume0.open_root_dir().unwrap(); | ||
| 67 | |||
| 68 | // Open a file called "MY_FILE.TXT" in the root directory | ||
| 69 | // This mutably borrows the directory. | ||
| 70 | let mut my_file = root_dir | ||
| 71 | .open_file_in_dir("MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly) | ||
| 72 | .unwrap(); | ||
| 73 | |||
| 74 | // Print the contents of the file | ||
| 75 | while !my_file.is_eof() { | ||
| 76 | let mut buf = [0u8; 32]; | ||
| 77 | if let Ok(n) = my_file.read(&mut buf) { | ||
| 78 | info!("{:a}", buf[..n]); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | loop {} | ||
| 83 | } | ||
diff --git a/examples/rp/src/bin/uart_buffered_split.rs b/examples/rp/src/bin/uart_buffered_split.rs index fac61aa04..468d2b61a 100644 --- a/examples/rp/src/bin/uart_buffered_split.rs +++ b/examples/rp/src/bin/uart_buffered_split.rs | |||
| @@ -31,7 +31,7 @@ async fn main(spawner: Spawner) { | |||
| 31 | static RX_BUF: StaticCell<[u8; 16]> = StaticCell::new(); | 31 | static RX_BUF: StaticCell<[u8; 16]> = StaticCell::new(); |
| 32 | let rx_buf = &mut RX_BUF.init([0; 16])[..]; | 32 | let rx_buf = &mut RX_BUF.init([0; 16])[..]; |
| 33 | let uart = BufferedUart::new(uart, Irqs, tx_pin, rx_pin, tx_buf, rx_buf, Config::default()); | 33 | let uart = BufferedUart::new(uart, Irqs, tx_pin, rx_pin, tx_buf, rx_buf, Config::default()); |
| 34 | let (rx, mut tx) = uart.split(); | 34 | let (mut tx, rx) = uart.split(); |
| 35 | 35 | ||
| 36 | unwrap!(spawner.spawn(reader(rx))); | 36 | unwrap!(spawner.spawn(reader(rx))); |
| 37 | 37 | ||
diff --git a/examples/rp/src/bin/uart_r503.rs b/examples/rp/src/bin/uart_r503.rs new file mode 100644 index 000000000..085be280b --- /dev/null +++ b/examples/rp/src/bin/uart_r503.rs | |||
| @@ -0,0 +1,158 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use defmt::{debug, error, info}; | ||
| 5 | use embassy_executor::Spawner; | ||
| 6 | use embassy_rp::bind_interrupts; | ||
| 7 | use embassy_rp::peripherals::UART0; | ||
| 8 | use embassy_rp::uart::{Config, DataBits, InterruptHandler as UARTInterruptHandler, Parity, StopBits, Uart}; | ||
| 9 | use embassy_time::{with_timeout, Duration, Timer}; | ||
| 10 | use heapless::Vec; | ||
| 11 | use {defmt_rtt as _, panic_probe as _}; | ||
| 12 | |||
| 13 | bind_interrupts!(pub struct Irqs { | ||
| 14 | UART0_IRQ => UARTInterruptHandler<UART0>; | ||
| 15 | }); | ||
| 16 | |||
| 17 | const START: u16 = 0xEF01; | ||
| 18 | const ADDRESS: u32 = 0xFFFFFFFF; | ||
| 19 | |||
| 20 | // ================================================================================ | ||
| 21 | |||
| 22 | // Data package format | ||
| 23 | // Name Length Description | ||
| 24 | // ========================================================================================================== | ||
| 25 | // Start 2 bytes Fixed value of 0xEF01; High byte transferred first. | ||
| 26 | // Address 4 bytes Default value is 0xFFFFFFFF, which can be modified by command. | ||
| 27 | // High byte transferred first and at wrong adder value, module | ||
| 28 | // will reject to transfer. | ||
| 29 | // PID 1 byte 01H Command packet; | ||
| 30 | // 02H Data packet; Data packet shall not appear alone in executing | ||
| 31 | // processs, must follow command packet or acknowledge packet. | ||
| 32 | // 07H Acknowledge packet; | ||
| 33 | // 08H End of Data packet. | ||
| 34 | // LENGTH 2 bytes Refers to the length of package content (command packets and data packets) | ||
| 35 | // plus the length of Checksum (2 bytes). Unit is byte. Max length is 256 bytes. | ||
| 36 | // And high byte is transferred first. | ||
| 37 | // DATA - It can be commands, data, command’s parameters, acknowledge result, etc. | ||
| 38 | // (fingerprint character value, template are all deemed as data); | ||
| 39 | // SUM 2 bytes The arithmetic sum of package identifier, package length and all package | ||
| 40 | // contens. Overflowing bits are omitted. high byte is transferred first. | ||
| 41 | |||
| 42 | // ================================================================================ | ||
| 43 | |||
| 44 | // Checksum is calculated on 'length (2 bytes) + data (??)'. | ||
| 45 | fn compute_checksum(buf: Vec<u8, 32>) -> u16 { | ||
| 46 | let mut checksum = 0u16; | ||
| 47 | |||
| 48 | let check_end = buf.len(); | ||
| 49 | let checked_bytes = &buf[6..check_end]; | ||
| 50 | for byte in checked_bytes { | ||
| 51 | checksum += (*byte) as u16; | ||
| 52 | } | ||
| 53 | return checksum; | ||
| 54 | } | ||
| 55 | |||
| 56 | #[embassy_executor::main] | ||
| 57 | async fn main(_spawner: Spawner) { | ||
| 58 | info!("Start"); | ||
| 59 | |||
| 60 | let p = embassy_rp::init(Default::default()); | ||
| 61 | |||
| 62 | // Initialize the fingerprint scanner. | ||
| 63 | let mut config = Config::default(); | ||
| 64 | config.baudrate = 57600; | ||
| 65 | config.stop_bits = StopBits::STOP1; | ||
| 66 | config.data_bits = DataBits::DataBits8; | ||
| 67 | config.parity = Parity::ParityNone; | ||
| 68 | |||
| 69 | let (uart, tx_pin, tx_dma, rx_pin, rx_dma) = (p.UART0, p.PIN_16, p.DMA_CH0, p.PIN_17, p.DMA_CH1); | ||
| 70 | let uart = Uart::new(uart, tx_pin, rx_pin, Irqs, tx_dma, rx_dma, config); | ||
| 71 | let (mut tx, mut rx) = uart.split(); | ||
| 72 | |||
| 73 | let mut vec_buf: Vec<u8, 32> = heapless::Vec::new(); | ||
| 74 | let mut data: Vec<u8, 32> = heapless::Vec::new(); | ||
| 75 | |||
| 76 | let mut speeds: Vec<u8, 3> = heapless::Vec::new(); | ||
| 77 | let _ = speeds.push(0xC8); // Slow | ||
| 78 | let _ = speeds.push(0x20); // Medium | ||
| 79 | let _ = speeds.push(0x02); // Fast | ||
| 80 | |||
| 81 | // Cycle through the three colours Red, Blue and Purple forever. | ||
| 82 | loop { | ||
| 83 | for colour in 1..=3 { | ||
| 84 | for speed in &speeds { | ||
| 85 | // Set the data first, because the length is dependent on that. | ||
| 86 | // However, we write the length bits before we do the data. | ||
| 87 | data.clear(); | ||
| 88 | let _ = data.push(0x01); // ctrl=Breathing light | ||
| 89 | let _ = data.push(*speed); | ||
| 90 | let _ = data.push(colour as u8); // colour=Red, Blue, Purple | ||
| 91 | let _ = data.push(0x00); // times=Infinite | ||
| 92 | |||
| 93 | // Clear buffers | ||
| 94 | vec_buf.clear(); | ||
| 95 | |||
| 96 | // START | ||
| 97 | let _ = vec_buf.extend_from_slice(&START.to_be_bytes()[..]); | ||
| 98 | |||
| 99 | // ADDRESS | ||
| 100 | let _ = vec_buf.extend_from_slice(&ADDRESS.to_be_bytes()[..]); | ||
| 101 | |||
| 102 | // PID | ||
| 103 | let _ = vec_buf.extend_from_slice(&[0x01]); | ||
| 104 | |||
| 105 | // LENGTH | ||
| 106 | let len: u16 = (1 + data.len() + 2).try_into().unwrap(); | ||
| 107 | let _ = vec_buf.extend_from_slice(&len.to_be_bytes()[..]); | ||
| 108 | |||
| 109 | // COMMAND | ||
| 110 | let _ = vec_buf.push(0x35); // Command: AuraLedConfig | ||
| 111 | |||
| 112 | // DATA | ||
| 113 | let _ = vec_buf.extend_from_slice(&data); | ||
| 114 | |||
| 115 | // SUM | ||
| 116 | let chk = compute_checksum(vec_buf.clone()); | ||
| 117 | let _ = vec_buf.extend_from_slice(&chk.to_be_bytes()[..]); | ||
| 118 | |||
| 119 | // ===== | ||
| 120 | |||
| 121 | // Send command buffer. | ||
| 122 | let data_write: [u8; 16] = vec_buf.clone().into_array().unwrap(); | ||
| 123 | debug!(" write='{:?}'", data_write[..]); | ||
| 124 | match tx.write(&data_write).await { | ||
| 125 | Ok(..) => info!("Write successful."), | ||
| 126 | Err(e) => error!("Write error: {:?}", e), | ||
| 127 | } | ||
| 128 | |||
| 129 | // ===== | ||
| 130 | |||
| 131 | // Read command buffer. | ||
| 132 | let mut read_buf: [u8; 1] = [0; 1]; // Can only read one byte at a time! | ||
| 133 | let mut data_read: Vec<u8, 32> = heapless::Vec::new(); // Save buffer. | ||
| 134 | |||
| 135 | info!("Attempting read."); | ||
| 136 | loop { | ||
| 137 | // Some commands, like `Img2Tz()` needs longer, but we hard-code this to 200ms | ||
| 138 | // for this command. | ||
| 139 | match with_timeout(Duration::from_millis(200), rx.read(&mut read_buf)).await { | ||
| 140 | Ok(..) => { | ||
| 141 | // Extract and save read byte. | ||
| 142 | debug!(" r='{=u8:#04x}H' ({:03}D)", read_buf[0], read_buf[0]); | ||
| 143 | let _ = data_read.push(read_buf[0]).unwrap(); | ||
| 144 | } | ||
| 145 | Err(..) => break, // TimeoutError -> Ignore. | ||
| 146 | } | ||
| 147 | } | ||
| 148 | info!("Read successful"); | ||
| 149 | debug!(" read='{:?}'", data_read[..]); | ||
| 150 | |||
| 151 | Timer::after_secs(3).await; | ||
| 152 | info!("Changing speed."); | ||
| 153 | } | ||
| 154 | |||
| 155 | info!("Changing colour."); | ||
| 156 | } | ||
| 157 | } | ||
| 158 | } | ||
diff --git a/examples/rp/src/bin/usb_ethernet.rs b/examples/rp/src/bin/usb_ethernet.rs index 01f0d5967..9a15125d4 100644 --- a/examples/rp/src/bin/usb_ethernet.rs +++ b/examples/rp/src/bin/usb_ethernet.rs | |||
| @@ -8,7 +8,8 @@ | |||
| 8 | use defmt::*; | 8 | use defmt::*; |
| 9 | use embassy_executor::Spawner; | 9 | use embassy_executor::Spawner; |
| 10 | use embassy_net::tcp::TcpSocket; | 10 | use embassy_net::tcp::TcpSocket; |
| 11 | use embassy_net::{Stack, StackResources}; | 11 | use embassy_net::StackResources; |
| 12 | use embassy_rp::clocks::RoscRng; | ||
| 12 | use embassy_rp::peripherals::USB; | 13 | use embassy_rp::peripherals::USB; |
| 13 | use embassy_rp::usb::{Driver, InterruptHandler}; | 14 | use embassy_rp::usb::{Driver, InterruptHandler}; |
| 14 | use embassy_rp::{bind_interrupts, peripherals}; | 15 | use embassy_rp::{bind_interrupts, peripherals}; |
| @@ -16,6 +17,7 @@ use embassy_usb::class::cdc_ncm::embassy_net::{Device, Runner, State as NetState | |||
| 16 | use embassy_usb::class::cdc_ncm::{CdcNcmClass, State}; | 17 | use embassy_usb::class::cdc_ncm::{CdcNcmClass, State}; |
| 17 | use embassy_usb::{Builder, Config, UsbDevice}; | 18 | use embassy_usb::{Builder, Config, UsbDevice}; |
| 18 | use embedded_io_async::Write; | 19 | use embedded_io_async::Write; |
| 20 | use rand::RngCore; | ||
| 19 | use static_cell::StaticCell; | 21 | use static_cell::StaticCell; |
| 20 | use {defmt_rtt as _, panic_probe as _}; | 22 | use {defmt_rtt as _, panic_probe as _}; |
| 21 | 23 | ||
| @@ -38,13 +40,14 @@ async fn usb_ncm_task(class: Runner<'static, MyDriver, MTU>) -> ! { | |||
| 38 | } | 40 | } |
| 39 | 41 | ||
| 40 | #[embassy_executor::task] | 42 | #[embassy_executor::task] |
| 41 | async fn net_task(stack: &'static Stack<Device<'static, MTU>>) -> ! { | 43 | async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static, MTU>>) -> ! { |
| 42 | stack.run().await | 44 | runner.run().await |
| 43 | } | 45 | } |
| 44 | 46 | ||
| 45 | #[embassy_executor::main] | 47 | #[embassy_executor::main] |
| 46 | async fn main(spawner: Spawner) { | 48 | async fn main(spawner: Spawner) { |
| 47 | let p = embassy_rp::init(Default::default()); | 49 | let p = embassy_rp::init(Default::default()); |
| 50 | let mut rng = RoscRng; | ||
| 48 | 51 | ||
| 49 | // Create the driver, from the HAL. | 52 | // Create the driver, from the HAL. |
| 50 | let driver = Driver::new(p.USB, Irqs); | 53 | let driver = Driver::new(p.USB, Irqs); |
| @@ -64,14 +67,12 @@ async fn main(spawner: Spawner) { | |||
| 64 | config.device_protocol = 0x01; | 67 | config.device_protocol = 0x01; |
| 65 | 68 | ||
| 66 | // Create embassy-usb DeviceBuilder using the driver and config. | 69 | // Create embassy-usb DeviceBuilder using the driver and config. |
| 67 | static DEVICE_DESC: StaticCell<[u8; 256]> = StaticCell::new(); | ||
| 68 | static CONFIG_DESC: StaticCell<[u8; 256]> = StaticCell::new(); | 70 | static CONFIG_DESC: StaticCell<[u8; 256]> = StaticCell::new(); |
| 69 | static BOS_DESC: StaticCell<[u8; 256]> = StaticCell::new(); | 71 | static BOS_DESC: StaticCell<[u8; 256]> = StaticCell::new(); |
| 70 | static CONTROL_BUF: StaticCell<[u8; 128]> = StaticCell::new(); | 72 | static CONTROL_BUF: StaticCell<[u8; 128]> = StaticCell::new(); |
| 71 | let mut builder = Builder::new( | 73 | let mut builder = Builder::new( |
| 72 | driver, | 74 | driver, |
| 73 | config, | 75 | config, |
| 74 | &mut DEVICE_DESC.init([0; 256])[..], | ||
| 75 | &mut CONFIG_DESC.init([0; 256])[..], | 76 | &mut CONFIG_DESC.init([0; 256])[..], |
| 76 | &mut BOS_DESC.init([0; 256])[..], | 77 | &mut BOS_DESC.init([0; 256])[..], |
| 77 | &mut [], // no msos descriptors | 78 | &mut [], // no msos descriptors |
| @@ -104,19 +105,13 @@ async fn main(spawner: Spawner) { | |||
| 104 | //}); | 105 | //}); |
| 105 | 106 | ||
| 106 | // Generate random seed | 107 | // Generate random seed |
| 107 | let seed = 1234; // guaranteed random, chosen by a fair dice roll | 108 | let seed = rng.next_u64(); |
| 108 | 109 | ||
| 109 | // Init network stack | 110 | // Init network stack |
| 110 | static STACK: StaticCell<Stack<Device<'static, MTU>>> = StaticCell::new(); | 111 | static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new(); |
| 111 | static RESOURCES: StaticCell<StackResources<2>> = StaticCell::new(); | 112 | let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); |
| 112 | let stack = &*STACK.init(Stack::new( | ||
| 113 | device, | ||
| 114 | config, | ||
| 115 | RESOURCES.init(StackResources::<2>::new()), | ||
| 116 | seed, | ||
| 117 | )); | ||
| 118 | 113 | ||
| 119 | unwrap!(spawner.spawn(net_task(stack))); | 114 | unwrap!(spawner.spawn(net_task(runner))); |
| 120 | 115 | ||
| 121 | // And now we can use it! | 116 | // And now we can use it! |
| 122 | 117 | ||
diff --git a/examples/rp/src/bin/usb_hid_keyboard.rs b/examples/rp/src/bin/usb_hid_keyboard.rs index b5ac16245..a7cb322d8 100644 --- a/examples/rp/src/bin/usb_hid_keyboard.rs +++ b/examples/rp/src/bin/usb_hid_keyboard.rs | |||
| @@ -36,13 +36,12 @@ async fn main(_spawner: Spawner) { | |||
| 36 | 36 | ||
| 37 | // Create embassy-usb DeviceBuilder using the driver and config. | 37 | // Create embassy-usb DeviceBuilder using the driver and config. |
| 38 | // It needs some buffers for building the descriptors. | 38 | // It needs some buffers for building the descriptors. |
| 39 | let mut device_descriptor = [0; 256]; | ||
| 40 | let mut config_descriptor = [0; 256]; | 39 | let mut config_descriptor = [0; 256]; |
| 41 | let mut bos_descriptor = [0; 256]; | 40 | let mut bos_descriptor = [0; 256]; |
| 42 | // You can also add a Microsoft OS descriptor. | 41 | // You can also add a Microsoft OS descriptor. |
| 43 | let mut msos_descriptor = [0; 256]; | 42 | let mut msos_descriptor = [0; 256]; |
| 44 | let mut control_buf = [0; 64]; | 43 | let mut control_buf = [0; 64]; |
| 45 | let request_handler = MyRequestHandler {}; | 44 | let mut request_handler = MyRequestHandler {}; |
| 46 | let mut device_handler = MyDeviceHandler::new(); | 45 | let mut device_handler = MyDeviceHandler::new(); |
| 47 | 46 | ||
| 48 | let mut state = State::new(); | 47 | let mut state = State::new(); |
| @@ -50,7 +49,6 @@ async fn main(_spawner: Spawner) { | |||
| 50 | let mut builder = Builder::new( | 49 | let mut builder = Builder::new( |
| 51 | driver, | 50 | driver, |
| 52 | config, | 51 | config, |
| 53 | &mut device_descriptor, | ||
| 54 | &mut config_descriptor, | 52 | &mut config_descriptor, |
| 55 | &mut bos_descriptor, | 53 | &mut bos_descriptor, |
| 56 | &mut msos_descriptor, | 54 | &mut msos_descriptor, |
| @@ -62,7 +60,7 @@ async fn main(_spawner: Spawner) { | |||
| 62 | // Create classes on the builder. | 60 | // Create classes on the builder. |
| 63 | let config = embassy_usb::class::hid::Config { | 61 | let config = embassy_usb::class::hid::Config { |
| 64 | report_descriptor: KeyboardReport::desc(), | 62 | report_descriptor: KeyboardReport::desc(), |
| 65 | request_handler: Some(&request_handler), | 63 | request_handler: None, |
| 66 | poll_ms: 60, | 64 | poll_ms: 60, |
| 67 | max_packet_size: 64, | 65 | max_packet_size: 64, |
| 68 | }; | 66 | }; |
| @@ -116,7 +114,7 @@ async fn main(_spawner: Spawner) { | |||
| 116 | }; | 114 | }; |
| 117 | 115 | ||
| 118 | let out_fut = async { | 116 | let out_fut = async { |
| 119 | reader.run(false, &request_handler).await; | 117 | reader.run(false, &mut request_handler).await; |
| 120 | }; | 118 | }; |
| 121 | 119 | ||
| 122 | // Run everything concurrently. | 120 | // Run everything concurrently. |
| @@ -127,21 +125,21 @@ async fn main(_spawner: Spawner) { | |||
| 127 | struct MyRequestHandler {} | 125 | struct MyRequestHandler {} |
| 128 | 126 | ||
| 129 | impl RequestHandler for MyRequestHandler { | 127 | impl RequestHandler for MyRequestHandler { |
| 130 | fn get_report(&self, id: ReportId, _buf: &mut [u8]) -> Option<usize> { | 128 | fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option<usize> { |
| 131 | info!("Get report for {:?}", id); | 129 | info!("Get report for {:?}", id); |
| 132 | None | 130 | None |
| 133 | } | 131 | } |
| 134 | 132 | ||
| 135 | fn set_report(&self, id: ReportId, data: &[u8]) -> OutResponse { | 133 | fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse { |
| 136 | info!("Set report for {:?}: {=[u8]}", id, data); | 134 | info!("Set report for {:?}: {=[u8]}", id, data); |
| 137 | OutResponse::Accepted | 135 | OutResponse::Accepted |
| 138 | } | 136 | } |
| 139 | 137 | ||
| 140 | fn set_idle_ms(&self, id: Option<ReportId>, dur: u32) { | 138 | fn set_idle_ms(&mut self, id: Option<ReportId>, dur: u32) { |
| 141 | info!("Set idle rate for {:?} to {:?}", id, dur); | 139 | info!("Set idle rate for {:?} to {:?}", id, dur); |
| 142 | } | 140 | } |
| 143 | 141 | ||
| 144 | fn get_idle_ms(&self, id: Option<ReportId>) -> Option<u32> { | 142 | fn get_idle_ms(&mut self, id: Option<ReportId>) -> Option<u32> { |
| 145 | info!("Get idle rate for {:?}", id); | 143 | info!("Get idle rate for {:?}", id); |
| 146 | None | 144 | None |
| 147 | } | 145 | } |
diff --git a/examples/rp/src/bin/usb_hid_mouse.rs b/examples/rp/src/bin/usb_hid_mouse.rs new file mode 100644 index 000000000..5ee650910 --- /dev/null +++ b/examples/rp/src/bin/usb_hid_mouse.rs | |||
| @@ -0,0 +1,173 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 5 | |||
| 6 | use defmt::*; | ||
| 7 | use embassy_executor::Spawner; | ||
| 8 | use embassy_futures::join::join; | ||
| 9 | use embassy_rp::bind_interrupts; | ||
| 10 | use embassy_rp::clocks::RoscRng; | ||
| 11 | use embassy_rp::peripherals::USB; | ||
| 12 | use embassy_rp::usb::{Driver, InterruptHandler}; | ||
| 13 | use embassy_time::Timer; | ||
| 14 | use embassy_usb::class::hid::{HidReaderWriter, ReportId, RequestHandler, State}; | ||
| 15 | use embassy_usb::control::OutResponse; | ||
| 16 | use embassy_usb::{Builder, Config, Handler}; | ||
| 17 | use rand::Rng; | ||
| 18 | use usbd_hid::descriptor::{MouseReport, SerializedDescriptor}; | ||
| 19 | use {defmt_rtt as _, panic_probe as _}; | ||
| 20 | |||
| 21 | bind_interrupts!(struct Irqs { | ||
| 22 | USBCTRL_IRQ => InterruptHandler<USB>; | ||
| 23 | }); | ||
| 24 | |||
| 25 | #[embassy_executor::main] | ||
| 26 | async fn main(_spawner: Spawner) { | ||
| 27 | let p = embassy_rp::init(Default::default()); | ||
| 28 | // Create the driver, from the HAL. | ||
| 29 | let driver = Driver::new(p.USB, Irqs); | ||
| 30 | |||
| 31 | // Create embassy-usb Config | ||
| 32 | let mut config = Config::new(0xc0de, 0xcafe); | ||
| 33 | config.manufacturer = Some("Embassy"); | ||
| 34 | config.product = Some("HID keyboard example"); | ||
| 35 | config.serial_number = Some("12345678"); | ||
| 36 | config.max_power = 100; | ||
| 37 | config.max_packet_size_0 = 64; | ||
| 38 | |||
| 39 | // Create embassy-usb DeviceBuilder using the driver and config. | ||
| 40 | // It needs some buffers for building the descriptors. | ||
| 41 | let mut config_descriptor = [0; 256]; | ||
| 42 | let mut bos_descriptor = [0; 256]; | ||
| 43 | // You can also add a Microsoft OS descriptor. | ||
| 44 | let mut msos_descriptor = [0; 256]; | ||
| 45 | let mut control_buf = [0; 64]; | ||
| 46 | let mut request_handler = MyRequestHandler {}; | ||
| 47 | let mut device_handler = MyDeviceHandler::new(); | ||
| 48 | |||
| 49 | let mut state = State::new(); | ||
| 50 | |||
| 51 | let mut builder = Builder::new( | ||
| 52 | driver, | ||
| 53 | config, | ||
| 54 | &mut config_descriptor, | ||
| 55 | &mut bos_descriptor, | ||
| 56 | &mut msos_descriptor, | ||
| 57 | &mut control_buf, | ||
| 58 | ); | ||
| 59 | |||
| 60 | builder.handler(&mut device_handler); | ||
| 61 | |||
| 62 | // Create classes on the builder. | ||
| 63 | let config = embassy_usb::class::hid::Config { | ||
| 64 | report_descriptor: MouseReport::desc(), | ||
| 65 | request_handler: None, | ||
| 66 | poll_ms: 60, | ||
| 67 | max_packet_size: 64, | ||
| 68 | }; | ||
| 69 | let hid = HidReaderWriter::<_, 1, 8>::new(&mut builder, &mut state, config); | ||
| 70 | |||
| 71 | // Build the builder. | ||
| 72 | let mut usb = builder.build(); | ||
| 73 | |||
| 74 | // Run the USB device. | ||
| 75 | let usb_fut = usb.run(); | ||
| 76 | |||
| 77 | let (reader, mut writer) = hid.split(); | ||
| 78 | |||
| 79 | // Do stuff with the class! | ||
| 80 | let in_fut = async { | ||
| 81 | let mut rng = RoscRng; | ||
| 82 | |||
| 83 | loop { | ||
| 84 | // every 1 second | ||
| 85 | _ = Timer::after_secs(1).await; | ||
| 86 | let report = MouseReport { | ||
| 87 | buttons: 0, | ||
| 88 | x: rng.gen_range(-100..100), // random small x movement | ||
| 89 | y: rng.gen_range(-100..100), // random small y movement | ||
| 90 | wheel: 0, | ||
| 91 | pan: 0, | ||
| 92 | }; | ||
| 93 | // Send the report. | ||
| 94 | match writer.write_serialize(&report).await { | ||
| 95 | Ok(()) => {} | ||
| 96 | Err(e) => warn!("Failed to send report: {:?}", e), | ||
| 97 | } | ||
| 98 | } | ||
| 99 | }; | ||
| 100 | |||
| 101 | let out_fut = async { | ||
| 102 | reader.run(false, &mut request_handler).await; | ||
| 103 | }; | ||
| 104 | |||
| 105 | // Run everything concurrently. | ||
| 106 | // If we had made everything `'static` above instead, we could do this using separate tasks instead. | ||
| 107 | join(usb_fut, join(in_fut, out_fut)).await; | ||
| 108 | } | ||
| 109 | |||
| 110 | struct MyRequestHandler {} | ||
| 111 | |||
| 112 | impl RequestHandler for MyRequestHandler { | ||
| 113 | fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option<usize> { | ||
| 114 | info!("Get report for {:?}", id); | ||
| 115 | None | ||
| 116 | } | ||
| 117 | |||
| 118 | fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse { | ||
| 119 | info!("Set report for {:?}: {=[u8]}", id, data); | ||
| 120 | OutResponse::Accepted | ||
| 121 | } | ||
| 122 | |||
| 123 | fn set_idle_ms(&mut self, id: Option<ReportId>, dur: u32) { | ||
| 124 | info!("Set idle rate for {:?} to {:?}", id, dur); | ||
| 125 | } | ||
| 126 | |||
| 127 | fn get_idle_ms(&mut self, id: Option<ReportId>) -> Option<u32> { | ||
| 128 | info!("Get idle rate for {:?}", id); | ||
| 129 | None | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | struct MyDeviceHandler { | ||
| 134 | configured: AtomicBool, | ||
| 135 | } | ||
| 136 | |||
| 137 | impl MyDeviceHandler { | ||
| 138 | fn new() -> Self { | ||
| 139 | MyDeviceHandler { | ||
| 140 | configured: AtomicBool::new(false), | ||
| 141 | } | ||
| 142 | } | ||
| 143 | } | ||
| 144 | |||
| 145 | impl Handler for MyDeviceHandler { | ||
| 146 | fn enabled(&mut self, enabled: bool) { | ||
| 147 | self.configured.store(false, Ordering::Relaxed); | ||
| 148 | if enabled { | ||
| 149 | info!("Device enabled"); | ||
| 150 | } else { | ||
| 151 | info!("Device disabled"); | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | fn reset(&mut self) { | ||
| 156 | self.configured.store(false, Ordering::Relaxed); | ||
| 157 | info!("Bus reset, the Vbus current limit is 100mA"); | ||
| 158 | } | ||
| 159 | |||
| 160 | fn addressed(&mut self, addr: u8) { | ||
| 161 | self.configured.store(false, Ordering::Relaxed); | ||
| 162 | info!("USB address set to: {}", addr); | ||
| 163 | } | ||
| 164 | |||
| 165 | fn configured(&mut self, configured: bool) { | ||
| 166 | self.configured.store(configured, Ordering::Relaxed); | ||
| 167 | if configured { | ||
| 168 | info!("Device configured, it may now draw up to the configured current limit from Vbus.") | ||
| 169 | } else { | ||
| 170 | info!("Device is no longer configured, the Vbus current limit is 100mA."); | ||
| 171 | } | ||
| 172 | } | ||
| 173 | } | ||
diff --git a/examples/rp/src/bin/usb_midi.rs b/examples/rp/src/bin/usb_midi.rs index 95306a35c..11db1b2e1 100644 --- a/examples/rp/src/bin/usb_midi.rs +++ b/examples/rp/src/bin/usb_midi.rs | |||
| @@ -46,7 +46,6 @@ async fn main(_spawner: Spawner) { | |||
| 46 | 46 | ||
| 47 | // Create embassy-usb DeviceBuilder using the driver and config. | 47 | // Create embassy-usb DeviceBuilder using the driver and config. |
| 48 | // It needs some buffers for building the descriptors. | 48 | // It needs some buffers for building the descriptors. |
| 49 | let mut device_descriptor = [0; 256]; | ||
| 50 | let mut config_descriptor = [0; 256]; | 49 | let mut config_descriptor = [0; 256]; |
| 51 | let mut bos_descriptor = [0; 256]; | 50 | let mut bos_descriptor = [0; 256]; |
| 52 | let mut control_buf = [0; 64]; | 51 | let mut control_buf = [0; 64]; |
| @@ -54,7 +53,6 @@ async fn main(_spawner: Spawner) { | |||
| 54 | let mut builder = Builder::new( | 53 | let mut builder = Builder::new( |
| 55 | driver, | 54 | driver, |
| 56 | config, | 55 | config, |
| 57 | &mut device_descriptor, | ||
| 58 | &mut config_descriptor, | 56 | &mut config_descriptor, |
| 59 | &mut bos_descriptor, | 57 | &mut bos_descriptor, |
| 60 | &mut [], // no msos descriptors | 58 | &mut [], // no msos descriptors |
diff --git a/examples/rp/src/bin/usb_raw.rs b/examples/rp/src/bin/usb_raw.rs index a6c8a5b2e..97e7e0244 100644 --- a/examples/rp/src/bin/usb_raw.rs +++ b/examples/rp/src/bin/usb_raw.rs | |||
| @@ -93,7 +93,6 @@ async fn main(_spawner: Spawner) { | |||
| 93 | 93 | ||
| 94 | // Create embassy-usb DeviceBuilder using the driver and config. | 94 | // Create embassy-usb DeviceBuilder using the driver and config. |
| 95 | // It needs some buffers for building the descriptors. | 95 | // It needs some buffers for building the descriptors. |
| 96 | let mut device_descriptor = [0; 256]; | ||
| 97 | let mut config_descriptor = [0; 256]; | 96 | let mut config_descriptor = [0; 256]; |
| 98 | let mut bos_descriptor = [0; 256]; | 97 | let mut bos_descriptor = [0; 256]; |
| 99 | let mut msos_descriptor = [0; 256]; | 98 | let mut msos_descriptor = [0; 256]; |
| @@ -106,7 +105,6 @@ async fn main(_spawner: Spawner) { | |||
| 106 | let mut builder = Builder::new( | 105 | let mut builder = Builder::new( |
| 107 | driver, | 106 | driver, |
| 108 | config, | 107 | config, |
| 109 | &mut device_descriptor, | ||
| 110 | &mut config_descriptor, | 108 | &mut config_descriptor, |
| 111 | &mut bos_descriptor, | 109 | &mut bos_descriptor, |
| 112 | &mut msos_descriptor, | 110 | &mut msos_descriptor, |
diff --git a/examples/rp/src/bin/usb_raw_bulk.rs b/examples/rp/src/bin/usb_raw_bulk.rs index 0dc8e9f72..331c3da4c 100644 --- a/examples/rp/src/bin/usb_raw_bulk.rs +++ b/examples/rp/src/bin/usb_raw_bulk.rs | |||
| @@ -71,7 +71,6 @@ async fn main(_spawner: Spawner) { | |||
| 71 | 71 | ||
| 72 | // Create embassy-usb DeviceBuilder using the driver and config. | 72 | // Create embassy-usb DeviceBuilder using the driver and config. |
| 73 | // It needs some buffers for building the descriptors. | 73 | // It needs some buffers for building the descriptors. |
| 74 | let mut device_descriptor = [0; 256]; | ||
| 75 | let mut config_descriptor = [0; 256]; | 74 | let mut config_descriptor = [0; 256]; |
| 76 | let mut bos_descriptor = [0; 256]; | 75 | let mut bos_descriptor = [0; 256]; |
| 77 | let mut msos_descriptor = [0; 256]; | 76 | let mut msos_descriptor = [0; 256]; |
| @@ -80,7 +79,6 @@ async fn main(_spawner: Spawner) { | |||
| 80 | let mut builder = Builder::new( | 79 | let mut builder = Builder::new( |
| 81 | driver, | 80 | driver, |
| 82 | config, | 81 | config, |
| 83 | &mut device_descriptor, | ||
| 84 | &mut config_descriptor, | 82 | &mut config_descriptor, |
| 85 | &mut bos_descriptor, | 83 | &mut bos_descriptor, |
| 86 | &mut msos_descriptor, | 84 | &mut msos_descriptor, |
diff --git a/examples/rp/src/bin/usb_serial.rs b/examples/rp/src/bin/usb_serial.rs index ab24a994c..4a802994a 100644 --- a/examples/rp/src/bin/usb_serial.rs +++ b/examples/rp/src/bin/usb_serial.rs | |||
| @@ -5,15 +5,15 @@ | |||
| 5 | #![no_std] | 5 | #![no_std] |
| 6 | #![no_main] | 6 | #![no_main] |
| 7 | 7 | ||
| 8 | use defmt::{info, panic}; | 8 | use defmt::{info, panic, unwrap}; |
| 9 | use embassy_executor::Spawner; | 9 | use embassy_executor::Spawner; |
| 10 | use embassy_futures::join::join; | ||
| 11 | use embassy_rp::bind_interrupts; | 10 | use embassy_rp::bind_interrupts; |
| 12 | use embassy_rp::peripherals::USB; | 11 | use embassy_rp::peripherals::USB; |
| 13 | use embassy_rp::usb::{Driver, Instance, InterruptHandler}; | 12 | use embassy_rp::usb::{Driver, Instance, InterruptHandler}; |
| 14 | use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; | 13 | use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; |
| 15 | use embassy_usb::driver::EndpointError; | 14 | use embassy_usb::driver::EndpointError; |
| 16 | use embassy_usb::{Builder, Config}; | 15 | use embassy_usb::UsbDevice; |
| 16 | use static_cell::StaticCell; | ||
| 17 | use {defmt_rtt as _, panic_probe as _}; | 17 | use {defmt_rtt as _, panic_probe as _}; |
| 18 | 18 | ||
| 19 | bind_interrupts!(struct Irqs { | 19 | bind_interrupts!(struct Irqs { |
| @@ -21,7 +21,7 @@ bind_interrupts!(struct Irqs { | |||
| 21 | }); | 21 | }); |
| 22 | 22 | ||
| 23 | #[embassy_executor::main] | 23 | #[embassy_executor::main] |
| 24 | async fn main(_spawner: Spawner) { | 24 | async fn main(spawner: Spawner) { |
| 25 | info!("Hello there!"); | 25 | info!("Hello there!"); |
| 26 | 26 | ||
| 27 | let p = embassy_rp::init(Default::default()); | 27 | let p = embassy_rp::init(Default::default()); |
| @@ -30,61 +30,69 @@ async fn main(_spawner: Spawner) { | |||
| 30 | let driver = Driver::new(p.USB, Irqs); | 30 | let driver = Driver::new(p.USB, Irqs); |
| 31 | 31 | ||
| 32 | // Create embassy-usb Config | 32 | // Create embassy-usb Config |
| 33 | let mut config = Config::new(0xc0de, 0xcafe); | 33 | let config = { |
| 34 | config.manufacturer = Some("Embassy"); | 34 | let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); |
| 35 | config.product = Some("USB-serial example"); | 35 | config.manufacturer = Some("Embassy"); |
| 36 | config.serial_number = Some("12345678"); | 36 | config.product = Some("USB-serial example"); |
| 37 | config.max_power = 100; | 37 | config.serial_number = Some("12345678"); |
| 38 | config.max_packet_size_0 = 64; | 38 | config.max_power = 100; |
| 39 | 39 | config.max_packet_size_0 = 64; | |
| 40 | // Required for windows compatibility. | 40 | |
| 41 | // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help | 41 | // Required for windows compatibility. |
| 42 | config.device_class = 0xEF; | 42 | // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help |
| 43 | config.device_sub_class = 0x02; | 43 | config.device_class = 0xEF; |
| 44 | config.device_protocol = 0x01; | 44 | config.device_sub_class = 0x02; |
| 45 | config.composite_with_iads = true; | 45 | config.device_protocol = 0x01; |
| 46 | config.composite_with_iads = true; | ||
| 47 | config | ||
| 48 | }; | ||
| 46 | 49 | ||
| 47 | // Create embassy-usb DeviceBuilder using the driver and config. | 50 | // Create embassy-usb DeviceBuilder using the driver and config. |
| 48 | // It needs some buffers for building the descriptors. | 51 | // It needs some buffers for building the descriptors. |
| 49 | let mut device_descriptor = [0; 256]; | 52 | let mut builder = { |
| 50 | let mut config_descriptor = [0; 256]; | 53 | static CONFIG_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new(); |
| 51 | let mut bos_descriptor = [0; 256]; | 54 | static BOS_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new(); |
| 52 | let mut control_buf = [0; 64]; | 55 | static CONTROL_BUF: StaticCell<[u8; 64]> = StaticCell::new(); |
| 53 | 56 | ||
| 54 | let mut state = State::new(); | 57 | let builder = embassy_usb::Builder::new( |
| 55 | 58 | driver, | |
| 56 | let mut builder = Builder::new( | 59 | config, |
| 57 | driver, | 60 | CONFIG_DESCRIPTOR.init([0; 256]), |
| 58 | config, | 61 | BOS_DESCRIPTOR.init([0; 256]), |
| 59 | &mut device_descriptor, | 62 | &mut [], // no msos descriptors |
| 60 | &mut config_descriptor, | 63 | CONTROL_BUF.init([0; 64]), |
| 61 | &mut bos_descriptor, | 64 | ); |
| 62 | &mut [], // no msos descriptors | 65 | builder |
| 63 | &mut control_buf, | 66 | }; |
| 64 | ); | ||
| 65 | 67 | ||
| 66 | // Create classes on the builder. | 68 | // Create classes on the builder. |
| 67 | let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); | 69 | let mut class = { |
| 70 | static STATE: StaticCell<State> = StaticCell::new(); | ||
| 71 | let state = STATE.init(State::new()); | ||
| 72 | CdcAcmClass::new(&mut builder, state, 64) | ||
| 73 | }; | ||
| 68 | 74 | ||
| 69 | // Build the builder. | 75 | // Build the builder. |
| 70 | let mut usb = builder.build(); | 76 | let usb = builder.build(); |
| 71 | 77 | ||
| 72 | // Run the USB device. | 78 | // Run the USB device. |
| 73 | let usb_fut = usb.run(); | 79 | unwrap!(spawner.spawn(usb_task(usb))); |
| 74 | 80 | ||
| 75 | // Do stuff with the class! | 81 | // Do stuff with the class! |
| 76 | let echo_fut = async { | 82 | loop { |
| 77 | loop { | 83 | class.wait_connection().await; |
| 78 | class.wait_connection().await; | 84 | info!("Connected"); |
| 79 | info!("Connected"); | 85 | let _ = echo(&mut class).await; |
| 80 | let _ = echo(&mut class).await; | 86 | info!("Disconnected"); |
| 81 | info!("Disconnected"); | 87 | } |
| 82 | } | 88 | } |
| 83 | }; | 89 | |
| 90 | type MyUsbDriver = Driver<'static, USB>; | ||
| 91 | type MyUsbDevice = UsbDevice<'static, MyUsbDriver>; | ||
| 84 | 92 | ||
| 85 | // Run everything concurrently. | 93 | #[embassy_executor::task] |
| 86 | // If we had made everything `'static` above instead, we could do this using separate tasks instead. | 94 | async fn usb_task(mut usb: MyUsbDevice) -> ! { |
| 87 | join(usb_fut, echo_fut).await; | 95 | usb.run().await |
| 88 | } | 96 | } |
| 89 | 97 | ||
| 90 | struct Disconnected {} | 98 | struct Disconnected {} |
diff --git a/examples/rp/src/bin/usb_serial_with_logger.rs b/examples/rp/src/bin/usb_serial_with_logger.rs index 4ba4fc25c..f9cfdef94 100644 --- a/examples/rp/src/bin/usb_serial_with_logger.rs +++ b/examples/rp/src/bin/usb_serial_with_logger.rs | |||
| @@ -46,7 +46,6 @@ async fn main(_spawner: Spawner) { | |||
| 46 | 46 | ||
| 47 | // Create embassy-usb DeviceBuilder using the driver and config. | 47 | // Create embassy-usb DeviceBuilder using the driver and config. |
| 48 | // It needs some buffers for building the descriptors. | 48 | // It needs some buffers for building the descriptors. |
| 49 | let mut device_descriptor = [0; 256]; | ||
| 50 | let mut config_descriptor = [0; 256]; | 49 | let mut config_descriptor = [0; 256]; |
| 51 | let mut bos_descriptor = [0; 256]; | 50 | let mut bos_descriptor = [0; 256]; |
| 52 | let mut control_buf = [0; 64]; | 51 | let mut control_buf = [0; 64]; |
| @@ -57,7 +56,6 @@ async fn main(_spawner: Spawner) { | |||
| 57 | let mut builder = Builder::new( | 56 | let mut builder = Builder::new( |
| 58 | driver, | 57 | driver, |
| 59 | config, | 58 | config, |
| 60 | &mut device_descriptor, | ||
| 61 | &mut config_descriptor, | 59 | &mut config_descriptor, |
| 62 | &mut bos_descriptor, | 60 | &mut bos_descriptor, |
| 63 | &mut [], // no msos descriptors | 61 | &mut [], // no msos descriptors |
diff --git a/examples/rp/src/bin/usb_webusb.rs b/examples/rp/src/bin/usb_webusb.rs new file mode 100644 index 000000000..e73938ac9 --- /dev/null +++ b/examples/rp/src/bin/usb_webusb.rs | |||
| @@ -0,0 +1,155 @@ | |||
| 1 | //! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip. | ||
| 2 | //! | ||
| 3 | //! This creates a WebUSB capable device that echoes data back to the host. | ||
| 4 | //! | ||
| 5 | //! To test this in the browser (ideally host this on localhost:8080, to test the landing page | ||
| 6 | //! feature): | ||
| 7 | //! ```js | ||
| 8 | //! (async () => { | ||
| 9 | //! const device = await navigator.usb.requestDevice({ filters: [{ vendorId: 0xf569 }] }); | ||
| 10 | //! await device.open(); | ||
| 11 | //! await device.claimInterface(1); | ||
| 12 | //! device.transferIn(1, 64).then(data => console.log(data)); | ||
| 13 | //! await device.transferOut(1, new Uint8Array([1,2,3])); | ||
| 14 | //! })(); | ||
| 15 | //! ``` | ||
| 16 | |||
| 17 | #![no_std] | ||
| 18 | #![no_main] | ||
| 19 | |||
| 20 | use defmt::info; | ||
| 21 | use embassy_executor::Spawner; | ||
| 22 | use embassy_futures::join::join; | ||
| 23 | use embassy_rp::bind_interrupts; | ||
| 24 | use embassy_rp::peripherals::USB; | ||
| 25 | use embassy_rp::usb::{Driver as UsbDriver, InterruptHandler}; | ||
| 26 | use embassy_usb::class::web_usb::{Config as WebUsbConfig, State, Url, WebUsb}; | ||
| 27 | use embassy_usb::driver::{Driver, Endpoint, EndpointIn, EndpointOut}; | ||
| 28 | use embassy_usb::msos::{self, windows_version}; | ||
| 29 | use embassy_usb::{Builder, Config}; | ||
| 30 | use {defmt_rtt as _, panic_probe as _}; | ||
| 31 | |||
| 32 | bind_interrupts!(struct Irqs { | ||
| 33 | USBCTRL_IRQ => InterruptHandler<USB>; | ||
| 34 | }); | ||
| 35 | |||
| 36 | // This is a randomly generated GUID to allow clients on Windows to find our device | ||
| 37 | const DEVICE_INTERFACE_GUIDS: &[&str] = &["{AFB9A6FB-30BA-44BC-9232-806CFC875321}"]; | ||
| 38 | |||
| 39 | #[embassy_executor::main] | ||
| 40 | async fn main(_spawner: Spawner) { | ||
| 41 | let p = embassy_rp::init(Default::default()); | ||
| 42 | |||
| 43 | // Create the driver, from the HAL. | ||
| 44 | let driver = UsbDriver::new(p.USB, Irqs); | ||
| 45 | |||
| 46 | // Create embassy-usb Config | ||
| 47 | let mut config = Config::new(0xf569, 0x0001); | ||
| 48 | config.manufacturer = Some("Embassy"); | ||
| 49 | config.product = Some("WebUSB example"); | ||
| 50 | config.serial_number = Some("12345678"); | ||
| 51 | config.max_power = 100; | ||
| 52 | config.max_packet_size_0 = 64; | ||
| 53 | |||
| 54 | // Required for windows compatibility. | ||
| 55 | // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help | ||
| 56 | config.device_class = 0xff; | ||
| 57 | config.device_sub_class = 0x00; | ||
| 58 | config.device_protocol = 0x00; | ||
| 59 | |||
| 60 | // Create embassy-usb DeviceBuilder using the driver and config. | ||
| 61 | // It needs some buffers for building the descriptors. | ||
| 62 | let mut config_descriptor = [0; 256]; | ||
| 63 | let mut bos_descriptor = [0; 256]; | ||
| 64 | let mut control_buf = [0; 64]; | ||
| 65 | let mut msos_descriptor = [0; 256]; | ||
| 66 | |||
| 67 | let webusb_config = WebUsbConfig { | ||
| 68 | max_packet_size: 64, | ||
| 69 | vendor_code: 1, | ||
| 70 | // If defined, shows a landing page which the device manufacturer would like the user to visit in order to control their device. Suggest the user to navigate to this URL when the device is connected. | ||
| 71 | landing_url: Some(Url::new("http://localhost:8080")), | ||
| 72 | }; | ||
| 73 | |||
| 74 | let mut state = State::new(); | ||
| 75 | |||
| 76 | let mut builder = Builder::new( | ||
| 77 | driver, | ||
| 78 | config, | ||
| 79 | &mut config_descriptor, | ||
| 80 | &mut bos_descriptor, | ||
| 81 | &mut msos_descriptor, | ||
| 82 | &mut control_buf, | ||
| 83 | ); | ||
| 84 | |||
| 85 | // Add the Microsoft OS Descriptor (MSOS/MOD) descriptor. | ||
| 86 | // We tell Windows that this entire device is compatible with the "WINUSB" feature, | ||
| 87 | // which causes it to use the built-in WinUSB driver automatically, which in turn | ||
| 88 | // can be used by libusb/rusb software without needing a custom driver or INF file. | ||
| 89 | // In principle you might want to call msos_feature() just on a specific function, | ||
| 90 | // if your device also has other functions that still use standard class drivers. | ||
| 91 | builder.msos_descriptor(windows_version::WIN8_1, 0); | ||
| 92 | builder.msos_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); | ||
| 93 | builder.msos_feature(msos::RegistryPropertyFeatureDescriptor::new( | ||
| 94 | "DeviceInterfaceGUIDs", | ||
| 95 | msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS), | ||
| 96 | )); | ||
| 97 | |||
| 98 | // Create classes on the builder (WebUSB just needs some setup, but doesn't return anything) | ||
| 99 | WebUsb::configure(&mut builder, &mut state, &webusb_config); | ||
| 100 | // Create some USB bulk endpoints for testing. | ||
| 101 | let mut endpoints = WebEndpoints::new(&mut builder, &webusb_config); | ||
| 102 | |||
| 103 | // Build the builder. | ||
| 104 | let mut usb = builder.build(); | ||
| 105 | |||
| 106 | // Run the USB device. | ||
| 107 | let usb_fut = usb.run(); | ||
| 108 | |||
| 109 | // Do some WebUSB transfers. | ||
| 110 | let webusb_fut = async { | ||
| 111 | loop { | ||
| 112 | endpoints.wait_connected().await; | ||
| 113 | info!("Connected"); | ||
| 114 | endpoints.echo().await; | ||
| 115 | } | ||
| 116 | }; | ||
| 117 | |||
| 118 | // Run everything concurrently. | ||
| 119 | // If we had made everything `'static` above instead, we could do this using separate tasks instead. | ||
| 120 | join(usb_fut, webusb_fut).await; | ||
| 121 | } | ||
| 122 | |||
| 123 | struct WebEndpoints<'d, D: Driver<'d>> { | ||
| 124 | write_ep: D::EndpointIn, | ||
| 125 | read_ep: D::EndpointOut, | ||
| 126 | } | ||
| 127 | |||
| 128 | impl<'d, D: Driver<'d>> WebEndpoints<'d, D> { | ||
| 129 | fn new(builder: &mut Builder<'d, D>, config: &'d WebUsbConfig<'d>) -> Self { | ||
| 130 | let mut func = builder.function(0xff, 0x00, 0x00); | ||
| 131 | let mut iface = func.interface(); | ||
| 132 | let mut alt = iface.alt_setting(0xff, 0x00, 0x00, None); | ||
| 133 | |||
| 134 | let write_ep = alt.endpoint_bulk_in(config.max_packet_size); | ||
| 135 | let read_ep = alt.endpoint_bulk_out(config.max_packet_size); | ||
| 136 | |||
| 137 | WebEndpoints { write_ep, read_ep } | ||
| 138 | } | ||
| 139 | |||
| 140 | // Wait until the device's endpoints are enabled. | ||
| 141 | async fn wait_connected(&mut self) { | ||
| 142 | self.read_ep.wait_enabled().await | ||
| 143 | } | ||
| 144 | |||
| 145 | // Echo data back to the host. | ||
| 146 | async fn echo(&mut self) { | ||
| 147 | let mut buf = [0; 64]; | ||
| 148 | loop { | ||
| 149 | let n = self.read_ep.read(&mut buf).await.unwrap(); | ||
| 150 | let data = &buf[..n]; | ||
| 151 | info!("Data read: {:x}", data); | ||
| 152 | self.write_ep.write(data).await.unwrap(); | ||
| 153 | } | ||
| 154 | } | ||
| 155 | } | ||
diff --git a/examples/rp/src/bin/wifi_ap_tcp_server.rs b/examples/rp/src/bin/wifi_ap_tcp_server.rs index b60852359..4c9651433 100644 --- a/examples/rp/src/bin/wifi_ap_tcp_server.rs +++ b/examples/rp/src/bin/wifi_ap_tcp_server.rs | |||
| @@ -11,13 +11,15 @@ use cyw43_pio::PioSpi; | |||
| 11 | use defmt::*; | 11 | use defmt::*; |
| 12 | use embassy_executor::Spawner; | 12 | use embassy_executor::Spawner; |
| 13 | use embassy_net::tcp::TcpSocket; | 13 | use embassy_net::tcp::TcpSocket; |
| 14 | use embassy_net::{Config, Stack, StackResources}; | 14 | use embassy_net::{Config, StackResources}; |
| 15 | use embassy_rp::bind_interrupts; | 15 | use embassy_rp::bind_interrupts; |
| 16 | use embassy_rp::clocks::RoscRng; | ||
| 16 | use embassy_rp::gpio::{Level, Output}; | 17 | use embassy_rp::gpio::{Level, Output}; |
| 17 | use embassy_rp::peripherals::{DMA_CH0, PIO0}; | 18 | use embassy_rp::peripherals::{DMA_CH0, PIO0}; |
| 18 | use embassy_rp::pio::{InterruptHandler, Pio}; | 19 | use embassy_rp::pio::{InterruptHandler, Pio}; |
| 19 | use embassy_time::Duration; | 20 | use embassy_time::Duration; |
| 20 | use embedded_io_async::Write; | 21 | use embedded_io_async::Write; |
| 22 | use rand::RngCore; | ||
| 21 | use static_cell::StaticCell; | 23 | use static_cell::StaticCell; |
| 22 | use {defmt_rtt as _, panic_probe as _}; | 24 | use {defmt_rtt as _, panic_probe as _}; |
| 23 | 25 | ||
| @@ -26,13 +28,13 @@ bind_interrupts!(struct Irqs { | |||
| 26 | }); | 28 | }); |
| 27 | 29 | ||
| 28 | #[embassy_executor::task] | 30 | #[embassy_executor::task] |
| 29 | async fn wifi_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { | 31 | async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { |
| 30 | runner.run().await | 32 | runner.run().await |
| 31 | } | 33 | } |
| 32 | 34 | ||
| 33 | #[embassy_executor::task] | 35 | #[embassy_executor::task] |
| 34 | async fn net_task(stack: &'static Stack<cyw43::NetDriver<'static>>) -> ! { | 36 | async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { |
| 35 | stack.run().await | 37 | runner.run().await |
| 36 | } | 38 | } |
| 37 | 39 | ||
| 38 | #[embassy_executor::main] | 40 | #[embassy_executor::main] |
| @@ -40,14 +42,15 @@ async fn main(spawner: Spawner) { | |||
| 40 | info!("Hello World!"); | 42 | info!("Hello World!"); |
| 41 | 43 | ||
| 42 | let p = embassy_rp::init(Default::default()); | 44 | let p = embassy_rp::init(Default::default()); |
| 45 | let mut rng = RoscRng; | ||
| 43 | 46 | ||
| 44 | let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); | 47 | let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); |
| 45 | let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); | 48 | let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); |
| 46 | 49 | ||
| 47 | // To make flashing faster for development, you may want to flash the firmwares independently | 50 | // To make flashing faster for development, you may want to flash the firmwares independently |
| 48 | // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: | 51 | // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: |
| 49 | // probe-rs download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 | 52 | // probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10100000 |
| 50 | // probe-rs download 43439A0_clm.bin --format bin --chip RP2040 --base-address 0x10140000 | 53 | // probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10140000 |
| 51 | //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 230321) }; | 54 | //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 230321) }; |
| 52 | //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; | 55 | //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; |
| 53 | 56 | ||
| @@ -59,7 +62,7 @@ async fn main(spawner: Spawner) { | |||
| 59 | static STATE: StaticCell<cyw43::State> = StaticCell::new(); | 62 | static STATE: StaticCell<cyw43::State> = StaticCell::new(); |
| 60 | let state = STATE.init(cyw43::State::new()); | 63 | let state = STATE.init(cyw43::State::new()); |
| 61 | let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; | 64 | let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; |
| 62 | unwrap!(spawner.spawn(wifi_task(runner))); | 65 | unwrap!(spawner.spawn(cyw43_task(runner))); |
| 63 | 66 | ||
| 64 | control.init(clm).await; | 67 | control.init(clm).await; |
| 65 | control | 68 | control |
| @@ -74,19 +77,13 @@ async fn main(spawner: Spawner) { | |||
| 74 | }); | 77 | }); |
| 75 | 78 | ||
| 76 | // Generate random seed | 79 | // Generate random seed |
| 77 | let seed = 0x0123_4567_89ab_cdef; // chosen by fair dice roll. guarenteed to be random. | 80 | let seed = rng.next_u64(); |
| 78 | 81 | ||
| 79 | // Init network stack | 82 | // Init network stack |
| 80 | static STACK: StaticCell<Stack<cyw43::NetDriver<'static>>> = StaticCell::new(); | 83 | static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new(); |
| 81 | static RESOURCES: StaticCell<StackResources<2>> = StaticCell::new(); | 84 | let (stack, runner) = embassy_net::new(net_device, config, RESOURCES.init(StackResources::new()), seed); |
| 82 | let stack = &*STACK.init(Stack::new( | 85 | |
| 83 | net_device, | 86 | unwrap!(spawner.spawn(net_task(runner))); |
| 84 | config, | ||
| 85 | RESOURCES.init(StackResources::<2>::new()), | ||
| 86 | seed, | ||
| 87 | )); | ||
| 88 | |||
| 89 | unwrap!(spawner.spawn(net_task(stack))); | ||
| 90 | 87 | ||
| 91 | //control.start_ap_open("cyw43", 5).await; | 88 | //control.start_ap_open("cyw43", 5).await; |
| 92 | control.start_ap_wpa2("cyw43", "password", 5).await; | 89 | control.start_ap_wpa2("cyw43", "password", 5).await; |
diff --git a/examples/rp/src/bin/wifi_blinky.rs b/examples/rp/src/bin/wifi_blinky.rs index 18eefe41f..04a61bbd5 100644 --- a/examples/rp/src/bin/wifi_blinky.rs +++ b/examples/rp/src/bin/wifi_blinky.rs | |||
| @@ -21,7 +21,7 @@ bind_interrupts!(struct Irqs { | |||
| 21 | }); | 21 | }); |
| 22 | 22 | ||
| 23 | #[embassy_executor::task] | 23 | #[embassy_executor::task] |
| 24 | async fn wifi_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { | 24 | async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { |
| 25 | runner.run().await | 25 | runner.run().await |
| 26 | } | 26 | } |
| 27 | 27 | ||
| @@ -33,8 +33,8 @@ async fn main(spawner: Spawner) { | |||
| 33 | 33 | ||
| 34 | // To make flashing faster for development, you may want to flash the firmwares independently | 34 | // To make flashing faster for development, you may want to flash the firmwares independently |
| 35 | // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: | 35 | // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: |
| 36 | // probe-rs download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 | 36 | // probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10100000 |
| 37 | // probe-rs download 43439A0_clm.bin --format bin --chip RP2040 --base-address 0x10140000 | 37 | // probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10140000 |
| 38 | //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 230321) }; | 38 | //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 230321) }; |
| 39 | //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; | 39 | //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; |
| 40 | 40 | ||
| @@ -46,7 +46,7 @@ async fn main(spawner: Spawner) { | |||
| 46 | static STATE: StaticCell<cyw43::State> = StaticCell::new(); | 46 | static STATE: StaticCell<cyw43::State> = StaticCell::new(); |
| 47 | let state = STATE.init(cyw43::State::new()); | 47 | let state = STATE.init(cyw43::State::new()); |
| 48 | let (_net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; | 48 | let (_net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; |
| 49 | unwrap!(spawner.spawn(wifi_task(runner))); | 49 | unwrap!(spawner.spawn(cyw43_task(runner))); |
| 50 | 50 | ||
| 51 | control.init(clm).await; | 51 | control.init(clm).await; |
| 52 | control | 52 | control |
diff --git a/examples/rp/src/bin/wifi_scan.rs b/examples/rp/src/bin/wifi_scan.rs index e0f85a6b0..434f0074c 100644 --- a/examples/rp/src/bin/wifi_scan.rs +++ b/examples/rp/src/bin/wifi_scan.rs | |||
| @@ -10,7 +10,6 @@ use core::str; | |||
| 10 | use cyw43_pio::PioSpi; | 10 | use cyw43_pio::PioSpi; |
| 11 | use defmt::*; | 11 | use defmt::*; |
| 12 | use embassy_executor::Spawner; | 12 | use embassy_executor::Spawner; |
| 13 | use embassy_net::Stack; | ||
| 14 | use embassy_rp::bind_interrupts; | 13 | use embassy_rp::bind_interrupts; |
| 15 | use embassy_rp::gpio::{Level, Output}; | 14 | use embassy_rp::gpio::{Level, Output}; |
| 16 | use embassy_rp::peripherals::{DMA_CH0, PIO0}; | 15 | use embassy_rp::peripherals::{DMA_CH0, PIO0}; |
| @@ -23,13 +22,13 @@ bind_interrupts!(struct Irqs { | |||
| 23 | }); | 22 | }); |
| 24 | 23 | ||
| 25 | #[embassy_executor::task] | 24 | #[embassy_executor::task] |
| 26 | async fn wifi_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { | 25 | async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { |
| 27 | runner.run().await | 26 | runner.run().await |
| 28 | } | 27 | } |
| 29 | 28 | ||
| 30 | #[embassy_executor::task] | 29 | #[embassy_executor::task] |
| 31 | async fn net_task(stack: &'static Stack<cyw43::NetDriver<'static>>) -> ! { | 30 | async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { |
| 32 | stack.run().await | 31 | runner.run().await |
| 33 | } | 32 | } |
| 34 | 33 | ||
| 35 | #[embassy_executor::main] | 34 | #[embassy_executor::main] |
| @@ -43,8 +42,8 @@ async fn main(spawner: Spawner) { | |||
| 43 | 42 | ||
| 44 | // To make flashing faster for development, you may want to flash the firmwares independently | 43 | // To make flashing faster for development, you may want to flash the firmwares independently |
| 45 | // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: | 44 | // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: |
| 46 | // probe-rs download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 | 45 | // probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10100000 |
| 47 | // probe-rs download 43439A0_clm.bin --format bin --chip RP2040 --base-address 0x10140000 | 46 | // probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10140000 |
| 48 | //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 230321) }; | 47 | //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 230321) }; |
| 49 | //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; | 48 | //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; |
| 50 | 49 | ||
| @@ -56,7 +55,7 @@ async fn main(spawner: Spawner) { | |||
| 56 | static STATE: StaticCell<cyw43::State> = StaticCell::new(); | 55 | static STATE: StaticCell<cyw43::State> = StaticCell::new(); |
| 57 | let state = STATE.init(cyw43::State::new()); | 56 | let state = STATE.init(cyw43::State::new()); |
| 58 | let (_net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; | 57 | let (_net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; |
| 59 | unwrap!(spawner.spawn(wifi_task(runner))); | 58 | unwrap!(spawner.spawn(cyw43_task(runner))); |
| 60 | 59 | ||
| 61 | control.init(clm).await; | 60 | control.init(clm).await; |
| 62 | control | 61 | control |
diff --git a/examples/rp/src/bin/wifi_tcp_server.rs b/examples/rp/src/bin/wifi_tcp_server.rs index f1afc4a00..7bf546e01 100644 --- a/examples/rp/src/bin/wifi_tcp_server.rs +++ b/examples/rp/src/bin/wifi_tcp_server.rs | |||
| @@ -7,17 +7,20 @@ | |||
| 7 | 7 | ||
| 8 | use core::str::from_utf8; | 8 | use core::str::from_utf8; |
| 9 | 9 | ||
| 10 | use cyw43::JoinOptions; | ||
| 10 | use cyw43_pio::PioSpi; | 11 | use cyw43_pio::PioSpi; |
| 11 | use defmt::*; | 12 | use defmt::*; |
| 12 | use embassy_executor::Spawner; | 13 | use embassy_executor::Spawner; |
| 13 | use embassy_net::tcp::TcpSocket; | 14 | use embassy_net::tcp::TcpSocket; |
| 14 | use embassy_net::{Config, Stack, StackResources}; | 15 | use embassy_net::{Config, StackResources}; |
| 15 | use embassy_rp::bind_interrupts; | 16 | use embassy_rp::bind_interrupts; |
| 17 | use embassy_rp::clocks::RoscRng; | ||
| 16 | use embassy_rp::gpio::{Level, Output}; | 18 | use embassy_rp::gpio::{Level, Output}; |
| 17 | use embassy_rp::peripherals::{DMA_CH0, PIO0}; | 19 | use embassy_rp::peripherals::{DMA_CH0, PIO0}; |
| 18 | use embassy_rp::pio::{InterruptHandler, Pio}; | 20 | use embassy_rp::pio::{InterruptHandler, Pio}; |
| 19 | use embassy_time::{Duration, Timer}; | 21 | use embassy_time::{Duration, Timer}; |
| 20 | use embedded_io_async::Write; | 22 | use embedded_io_async::Write; |
| 23 | use rand::RngCore; | ||
| 21 | use static_cell::StaticCell; | 24 | use static_cell::StaticCell; |
| 22 | use {defmt_rtt as _, panic_probe as _}; | 25 | use {defmt_rtt as _, panic_probe as _}; |
| 23 | 26 | ||
| @@ -25,17 +28,17 @@ bind_interrupts!(struct Irqs { | |||
| 25 | PIO0_IRQ_0 => InterruptHandler<PIO0>; | 28 | PIO0_IRQ_0 => InterruptHandler<PIO0>; |
| 26 | }); | 29 | }); |
| 27 | 30 | ||
| 28 | const WIFI_NETWORK: &str = "EmbassyTest"; | 31 | const WIFI_NETWORK: &str = "LadronDeWifi"; |
| 29 | const WIFI_PASSWORD: &str = "V8YxhKt5CdIAJFud"; | 32 | const WIFI_PASSWORD: &str = "MBfcaedHmyRFE4kaQ1O5SsY8"; |
| 30 | 33 | ||
| 31 | #[embassy_executor::task] | 34 | #[embassy_executor::task] |
| 32 | async fn wifi_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { | 35 | async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { |
| 33 | runner.run().await | 36 | runner.run().await |
| 34 | } | 37 | } |
| 35 | 38 | ||
| 36 | #[embassy_executor::task] | 39 | #[embassy_executor::task] |
| 37 | async fn net_task(stack: &'static Stack<cyw43::NetDriver<'static>>) -> ! { | 40 | async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { |
| 38 | stack.run().await | 41 | runner.run().await |
| 39 | } | 42 | } |
| 40 | 43 | ||
| 41 | #[embassy_executor::main] | 44 | #[embassy_executor::main] |
| @@ -43,14 +46,15 @@ async fn main(spawner: Spawner) { | |||
| 43 | info!("Hello World!"); | 46 | info!("Hello World!"); |
| 44 | 47 | ||
| 45 | let p = embassy_rp::init(Default::default()); | 48 | let p = embassy_rp::init(Default::default()); |
| 49 | let mut rng = RoscRng; | ||
| 46 | 50 | ||
| 47 | let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); | 51 | let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); |
| 48 | let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); | 52 | let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); |
| 49 | 53 | ||
| 50 | // To make flashing faster for development, you may want to flash the firmwares independently | 54 | // To make flashing faster for development, you may want to flash the firmwares independently |
| 51 | // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: | 55 | // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: |
| 52 | // probe-rs download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 | 56 | // probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10100000 |
| 53 | // probe-rs download 43439A0_clm.bin --format bin --chip RP2040 --base-address 0x10140000 | 57 | // probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10140000 |
| 54 | //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 230321) }; | 58 | //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 230321) }; |
| 55 | //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; | 59 | //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; |
| 56 | 60 | ||
| @@ -62,7 +66,7 @@ async fn main(spawner: Spawner) { | |||
| 62 | static STATE: StaticCell<cyw43::State> = StaticCell::new(); | 66 | static STATE: StaticCell<cyw43::State> = StaticCell::new(); |
| 63 | let state = STATE.init(cyw43::State::new()); | 67 | let state = STATE.init(cyw43::State::new()); |
| 64 | let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; | 68 | let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; |
| 65 | unwrap!(spawner.spawn(wifi_task(runner))); | 69 | unwrap!(spawner.spawn(cyw43_task(runner))); |
| 66 | 70 | ||
| 67 | control.init(clm).await; | 71 | control.init(clm).await; |
| 68 | control | 72 | control |
| @@ -77,23 +81,19 @@ async fn main(spawner: Spawner) { | |||
| 77 | //}); | 81 | //}); |
| 78 | 82 | ||
| 79 | // Generate random seed | 83 | // Generate random seed |
| 80 | let seed = 0x0123_4567_89ab_cdef; // chosen by fair dice roll. guarenteed to be random. | 84 | let seed = rng.next_u64(); |
| 81 | 85 | ||
| 82 | // Init network stack | 86 | // Init network stack |
| 83 | static STACK: StaticCell<Stack<cyw43::NetDriver<'static>>> = StaticCell::new(); | 87 | static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new(); |
| 84 | static RESOURCES: StaticCell<StackResources<2>> = StaticCell::new(); | 88 | let (stack, runner) = embassy_net::new(net_device, config, RESOURCES.init(StackResources::new()), seed); |
| 85 | let stack = &*STACK.init(Stack::new( | ||
| 86 | net_device, | ||
| 87 | config, | ||
| 88 | RESOURCES.init(StackResources::<2>::new()), | ||
| 89 | seed, | ||
| 90 | )); | ||
| 91 | 89 | ||
| 92 | unwrap!(spawner.spawn(net_task(stack))); | 90 | unwrap!(spawner.spawn(net_task(runner))); |
| 93 | 91 | ||
| 94 | loop { | 92 | loop { |
| 95 | //control.join_open(WIFI_NETWORK).await; | 93 | match control |
| 96 | match control.join_wpa2(WIFI_NETWORK, WIFI_PASSWORD).await { | 94 | .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) |
| 95 | .await | ||
| 96 | { | ||
| 97 | Ok(_) => break, | 97 | Ok(_) => break, |
| 98 | Err(err) => { | 98 | Err(err) => { |
| 99 | info!("join failed with status={}", err.status); | 99 | info!("join failed with status={}", err.status); |
diff --git a/examples/rp/src/bin/wifi_webrequest.rs b/examples/rp/src/bin/wifi_webrequest.rs new file mode 100644 index 000000000..1ae909917 --- /dev/null +++ b/examples/rp/src/bin/wifi_webrequest.rs | |||
| @@ -0,0 +1,190 @@ | |||
| 1 | //! This example uses the RP Pico W board Wifi chip (cyw43). | ||
| 2 | //! Connects to Wifi network and makes a web request to get the current time. | ||
| 3 | |||
| 4 | #![no_std] | ||
| 5 | #![no_main] | ||
| 6 | #![allow(async_fn_in_trait)] | ||
| 7 | |||
| 8 | use core::str::from_utf8; | ||
| 9 | |||
| 10 | use cyw43::JoinOptions; | ||
| 11 | use cyw43_pio::PioSpi; | ||
| 12 | use defmt::*; | ||
| 13 | use embassy_executor::Spawner; | ||
| 14 | use embassy_net::dns::DnsSocket; | ||
| 15 | use embassy_net::tcp::client::{TcpClient, TcpClientState}; | ||
| 16 | use embassy_net::{Config, StackResources}; | ||
| 17 | use embassy_rp::bind_interrupts; | ||
| 18 | use embassy_rp::clocks::RoscRng; | ||
| 19 | use embassy_rp::gpio::{Level, Output}; | ||
| 20 | use embassy_rp::peripherals::{DMA_CH0, PIO0}; | ||
| 21 | use embassy_rp::pio::{InterruptHandler, Pio}; | ||
| 22 | use embassy_time::{Duration, Timer}; | ||
| 23 | use rand::RngCore; | ||
| 24 | use reqwless::client::{HttpClient, TlsConfig, TlsVerify}; | ||
| 25 | use reqwless::request::Method; | ||
| 26 | use serde::Deserialize; | ||
| 27 | use static_cell::StaticCell; | ||
| 28 | use {defmt_rtt as _, panic_probe as _, serde_json_core}; | ||
| 29 | |||
| 30 | bind_interrupts!(struct Irqs { | ||
| 31 | PIO0_IRQ_0 => InterruptHandler<PIO0>; | ||
| 32 | }); | ||
| 33 | |||
| 34 | const WIFI_NETWORK: &str = "ssid"; // change to your network SSID | ||
| 35 | const WIFI_PASSWORD: &str = "pwd"; // change to your network password | ||
| 36 | |||
| 37 | #[embassy_executor::task] | ||
| 38 | async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { | ||
| 39 | runner.run().await | ||
| 40 | } | ||
| 41 | |||
| 42 | #[embassy_executor::task] | ||
| 43 | async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { | ||
| 44 | runner.run().await | ||
| 45 | } | ||
| 46 | |||
| 47 | #[embassy_executor::main] | ||
| 48 | async fn main(spawner: Spawner) { | ||
| 49 | info!("Hello World!"); | ||
| 50 | |||
| 51 | let p = embassy_rp::init(Default::default()); | ||
| 52 | let mut rng = RoscRng; | ||
| 53 | |||
| 54 | let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); | ||
| 55 | let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); | ||
| 56 | // To make flashing faster for development, you may want to flash the firmwares independently | ||
| 57 | // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: | ||
| 58 | // probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10100000 | ||
| 59 | // probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10140000 | ||
| 60 | // let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 230321) }; | ||
| 61 | // let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; | ||
| 62 | |||
| 63 | let pwr = Output::new(p.PIN_23, Level::Low); | ||
| 64 | let cs = Output::new(p.PIN_25, Level::High); | ||
| 65 | let mut pio = Pio::new(p.PIO0, Irqs); | ||
| 66 | let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); | ||
| 67 | |||
| 68 | static STATE: StaticCell<cyw43::State> = StaticCell::new(); | ||
| 69 | let state = STATE.init(cyw43::State::new()); | ||
| 70 | let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; | ||
| 71 | unwrap!(spawner.spawn(cyw43_task(runner))); | ||
| 72 | |||
| 73 | control.init(clm).await; | ||
| 74 | control | ||
| 75 | .set_power_management(cyw43::PowerManagementMode::PowerSave) | ||
| 76 | .await; | ||
| 77 | |||
| 78 | let config = Config::dhcpv4(Default::default()); | ||
| 79 | // Use static IP configuration instead of DHCP | ||
| 80 | //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { | ||
| 81 | // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), | ||
| 82 | // dns_servers: Vec::new(), | ||
| 83 | // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), | ||
| 84 | //}); | ||
| 85 | |||
| 86 | // Generate random seed | ||
| 87 | let seed = rng.next_u64(); | ||
| 88 | |||
| 89 | // Init network stack | ||
| 90 | static RESOURCES: StaticCell<StackResources<5>> = StaticCell::new(); | ||
| 91 | let (stack, runner) = embassy_net::new(net_device, config, RESOURCES.init(StackResources::new()), seed); | ||
| 92 | |||
| 93 | unwrap!(spawner.spawn(net_task(runner))); | ||
| 94 | |||
| 95 | loop { | ||
| 96 | match control | ||
| 97 | .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) | ||
| 98 | .await | ||
| 99 | { | ||
| 100 | Ok(_) => break, | ||
| 101 | Err(err) => { | ||
| 102 | info!("join failed with status={}", err.status); | ||
| 103 | } | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | // Wait for DHCP, not necessary when using static IP | ||
| 108 | info!("waiting for DHCP..."); | ||
| 109 | while !stack.is_config_up() { | ||
| 110 | Timer::after_millis(100).await; | ||
| 111 | } | ||
| 112 | info!("DHCP is now up!"); | ||
| 113 | |||
| 114 | info!("waiting for link up..."); | ||
| 115 | while !stack.is_link_up() { | ||
| 116 | Timer::after_millis(500).await; | ||
| 117 | } | ||
| 118 | info!("Link is up!"); | ||
| 119 | |||
| 120 | info!("waiting for stack to be up..."); | ||
| 121 | stack.wait_config_up().await; | ||
| 122 | info!("Stack is up!"); | ||
| 123 | |||
| 124 | // And now we can use it! | ||
| 125 | |||
| 126 | loop { | ||
| 127 | let mut rx_buffer = [0; 8192]; | ||
| 128 | let mut tls_read_buffer = [0; 16640]; | ||
| 129 | let mut tls_write_buffer = [0; 16640]; | ||
| 130 | |||
| 131 | let client_state = TcpClientState::<1, 1024, 1024>::new(); | ||
| 132 | let tcp_client = TcpClient::new(stack, &client_state); | ||
| 133 | let dns_client = DnsSocket::new(stack); | ||
| 134 | let tls_config = TlsConfig::new(seed, &mut tls_read_buffer, &mut tls_write_buffer, TlsVerify::None); | ||
| 135 | |||
| 136 | let mut http_client = HttpClient::new_with_tls(&tcp_client, &dns_client, tls_config); | ||
| 137 | let url = "https://worldtimeapi.org/api/timezone/Europe/Berlin"; | ||
| 138 | // for non-TLS requests, use this instead: | ||
| 139 | // let mut http_client = HttpClient::new(&tcp_client, &dns_client); | ||
| 140 | // let url = "http://worldtimeapi.org/api/timezone/Europe/Berlin"; | ||
| 141 | |||
| 142 | info!("connecting to {}", &url); | ||
| 143 | |||
| 144 | let mut request = match http_client.request(Method::GET, &url).await { | ||
| 145 | Ok(req) => req, | ||
| 146 | Err(e) => { | ||
| 147 | error!("Failed to make HTTP request: {:?}", e); | ||
| 148 | return; // handle the error | ||
| 149 | } | ||
| 150 | }; | ||
| 151 | |||
| 152 | let response = match request.send(&mut rx_buffer).await { | ||
| 153 | Ok(resp) => resp, | ||
| 154 | Err(_e) => { | ||
| 155 | error!("Failed to send HTTP request"); | ||
| 156 | return; // handle the error; | ||
| 157 | } | ||
| 158 | }; | ||
| 159 | |||
| 160 | let body = match from_utf8(response.body().read_to_end().await.unwrap()) { | ||
| 161 | Ok(b) => b, | ||
| 162 | Err(_e) => { | ||
| 163 | error!("Failed to read response body"); | ||
| 164 | return; // handle the error | ||
| 165 | } | ||
| 166 | }; | ||
| 167 | info!("Response body: {:?}", &body); | ||
| 168 | |||
| 169 | // parse the response body and update the RTC | ||
| 170 | |||
| 171 | #[derive(Deserialize)] | ||
| 172 | struct ApiResponse<'a> { | ||
| 173 | datetime: &'a str, | ||
| 174 | // other fields as needed | ||
| 175 | } | ||
| 176 | |||
| 177 | let bytes = body.as_bytes(); | ||
| 178 | match serde_json_core::de::from_slice::<ApiResponse>(bytes) { | ||
| 179 | Ok((output, _used)) => { | ||
| 180 | info!("Datetime: {:?}", output.datetime); | ||
| 181 | } | ||
| 182 | Err(_e) => { | ||
| 183 | error!("Failed to parse response body"); | ||
| 184 | return; // handle the error | ||
| 185 | } | ||
| 186 | } | ||
| 187 | |||
| 188 | Timer::after(Duration::from_secs(5)).await; | ||
| 189 | } | ||
| 190 | } | ||
diff --git a/examples/rp/src/bin/zerocopy.rs b/examples/rp/src/bin/zerocopy.rs new file mode 100644 index 000000000..39f03c8e4 --- /dev/null +++ b/examples/rp/src/bin/zerocopy.rs | |||
| @@ -0,0 +1,94 @@ | |||
| 1 | //! This example shows how to use `zerocopy_channel` from `embassy_sync` for | ||
| 2 | //! sending large values between two tasks without copying. | ||
| 3 | //! The example also shows how to use the RP2040 ADC with DMA. | ||
| 4 | #![no_std] | ||
| 5 | #![no_main] | ||
| 6 | |||
| 7 | use core::sync::atomic::{AtomicU16, Ordering}; | ||
| 8 | |||
| 9 | use defmt::*; | ||
| 10 | use embassy_executor::Spawner; | ||
| 11 | use embassy_rp::adc::{self, Adc, Async, Config, InterruptHandler}; | ||
| 12 | use embassy_rp::bind_interrupts; | ||
| 13 | use embassy_rp::gpio::Pull; | ||
| 14 | use embassy_rp::peripherals::DMA_CH0; | ||
| 15 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 16 | use embassy_sync::zerocopy_channel::{Channel, Receiver, Sender}; | ||
| 17 | use embassy_time::{Duration, Ticker, Timer}; | ||
| 18 | use static_cell::StaticCell; | ||
| 19 | use {defmt_rtt as _, panic_probe as _}; | ||
| 20 | |||
| 21 | type SampleBuffer = [u16; 512]; | ||
| 22 | |||
| 23 | bind_interrupts!(struct Irqs { | ||
| 24 | ADC_IRQ_FIFO => InterruptHandler; | ||
| 25 | }); | ||
| 26 | |||
| 27 | const BLOCK_SIZE: usize = 512; | ||
| 28 | const NUM_BLOCKS: usize = 2; | ||
| 29 | static MAX: AtomicU16 = AtomicU16::new(0); | ||
| 30 | |||
| 31 | struct AdcParts { | ||
| 32 | adc: Adc<'static, Async>, | ||
| 33 | pin: adc::Channel<'static>, | ||
| 34 | dma: DMA_CH0, | ||
| 35 | } | ||
| 36 | |||
| 37 | #[embassy_executor::main] | ||
| 38 | async fn main(spawner: Spawner) { | ||
| 39 | let p = embassy_rp::init(Default::default()); | ||
| 40 | info!("Here we go!"); | ||
| 41 | |||
| 42 | let adc_parts = AdcParts { | ||
| 43 | adc: Adc::new(p.ADC, Irqs, Config::default()), | ||
| 44 | pin: adc::Channel::new_pin(p.PIN_29, Pull::None), | ||
| 45 | dma: p.DMA_CH0, | ||
| 46 | }; | ||
| 47 | |||
| 48 | static BUF: StaticCell<[SampleBuffer; NUM_BLOCKS]> = StaticCell::new(); | ||
| 49 | let buf = BUF.init([[0; BLOCK_SIZE]; NUM_BLOCKS]); | ||
| 50 | |||
| 51 | static CHANNEL: StaticCell<Channel<'_, NoopRawMutex, SampleBuffer>> = StaticCell::new(); | ||
| 52 | let channel = CHANNEL.init(Channel::new(buf)); | ||
| 53 | let (sender, receiver) = channel.split(); | ||
| 54 | |||
| 55 | spawner.must_spawn(consumer(receiver)); | ||
| 56 | spawner.must_spawn(producer(sender, adc_parts)); | ||
| 57 | |||
| 58 | let mut ticker = Ticker::every(Duration::from_secs(1)); | ||
| 59 | loop { | ||
| 60 | ticker.next().await; | ||
| 61 | let max = MAX.load(Ordering::Relaxed); | ||
| 62 | info!("latest block's max value: {:?}", max); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | #[embassy_executor::task] | ||
| 67 | async fn producer(mut sender: Sender<'static, NoopRawMutex, SampleBuffer>, mut adc: AdcParts) { | ||
| 68 | loop { | ||
| 69 | // Obtain a free buffer from the channel | ||
| 70 | let buf = sender.send().await; | ||
| 71 | |||
| 72 | // Fill it with data | ||
| 73 | adc.adc.read_many(&mut adc.pin, buf, 1, &mut adc.dma).await.unwrap(); | ||
| 74 | |||
| 75 | // Notify the channel that the buffer is now ready to be received | ||
| 76 | sender.send_done(); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | #[embassy_executor::task] | ||
| 81 | async fn consumer(mut receiver: Receiver<'static, NoopRawMutex, SampleBuffer>) { | ||
| 82 | loop { | ||
| 83 | // Receive a buffer from the channel | ||
| 84 | let buf = receiver.receive().await; | ||
| 85 | |||
| 86 | // Simulate using the data, while the producer is filling up the next buffer | ||
| 87 | Timer::after_micros(1000).await; | ||
| 88 | let max = buf.iter().max().unwrap(); | ||
| 89 | MAX.store(*max, Ordering::Relaxed); | ||
| 90 | |||
| 91 | // Notify the channel that the buffer is now ready to be reused | ||
| 92 | receiver.receive_done(); | ||
| 93 | } | ||
| 94 | } | ||
