├── .gitignore ├── .gitmodules ├── Cargo.toml ├── README.md ├── avatar-cli ├── Cargo.toml ├── examples │ └── benchmark.rs └── src │ └── main.rs ├── avatar-common ├── Cargo.toml └── src │ └── lib.rs ├── avatar-probe-rs ├── Cargo.toml └── src │ └── lib.rs ├── avatar-stm32f103c8 ├── .cargo │ └── config ├── Cargo.toml ├── build.rs ├── memory.x ├── openocd.cfg ├── openocd.gdb └── src │ └── main.rs ├── avatar-usb ├── Cargo.toml └── src │ ├── class.rs │ ├── host.rs │ └── lib.rs ├── examples-blue-pill ├── Cargo.toml ├── examples │ └── blinky.rs └── src │ └── lib.rs ├── examples-dwm1001-dev ├── Cargo.toml ├── examples │ └── blinky.rs └── src │ └── lib.rs └── examples-nucleo-f042k6 ├── Cargo.toml ├── examples ├── blinky.rs ├── i2c_bme280.rs ├── serial_hello.rs └── test_hpdl.rs └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .idea 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vcell"] 2 | path = vcell 3 | url = https://github.com/Disasm/vcell.git 4 | branch = avatar 5 | [submodule "stm32f0xx-hal"] 6 | path = stm32f0xx-hal 7 | url = https://github.com/Disasm/stm32f0xx-hal.git 8 | branch = avatar 9 | [submodule "cortex-m"] 10 | path = cortex-m 11 | url = https://github.com/Disasm/cortex-m.git 12 | branch = avatar 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "avatar-cli", 4 | "avatar-common", 5 | "avatar-probe-rs", 6 | "avatar-stm32f103c8", 7 | "avatar-usb", 8 | "cortex-m", 9 | "examples-blue-pill", 10 | "examples-dwm1001-dev", 11 | "examples-nucleo-f042k6", 12 | "stm32f0xx-hal", 13 | "vcell", 14 | ] 15 | 16 | [patch.crates-io] 17 | vcell = { path = "vcell" } 18 | cortex-m = { path = "cortex-m" } 19 | stm32f0xx-hal = { path = "stm32f0xx-hal" } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `avatar-rs` 2 | 3 | > A proof-of-concept project aimed at prototyping embedded applications on a host. 4 | 5 | ## Running the examples 6 | 7 | You will need a [NUCLEO-F042K6] board to run the examples for it. 8 | 9 | Clone the repository: 10 | 11 | git clone --recursive https://github.com/Disasm/avatar-rs 12 | cd avatar-rs 13 | 14 | Connect your NUCLEO-F042K6 with a USB cable. 15 | 16 | Run the `blinky` example: 17 | 18 | cd examples-nucleo-f042k6 19 | cargo run --example blinky 20 | 21 | See also [`serial_hello`] and [`i2c_bme280`] examples. 22 | 23 | [NUCLEO-F042K6]: https://www.st.com/en/evaluation-tools/nucleo-f042k6.html 24 | [`serial_hello`]: https://github.com/Disasm/avatar-rs/blob/master/examples-nucleo-f042k6/examples/serial_hello.rs 25 | [`i2c_bme280`]: https://github.com/Disasm/avatar-rs/blob/master/examples-nucleo-f042k6/examples/i2c_bme280.rs 26 | 27 | 28 | ## Porting to the different families 29 | 30 | Peripheral access crates don't require any changes if generated with svd2rust 0.16.1 31 | 32 | HAL crates require changes if they use the following constructions. 33 | 34 | ### `volatile_read`/`volatile_write` 35 | 36 | All the volatile memory accesses should be replaced with calls to the `VolatileCell` methods. 37 | 38 | For example, 39 | 40 | unsafe { ptr::read_volatile(&self.spi.dr as *const _ as *const u8) } 41 | 42 | should be replaced with 43 | 44 | unsafe { (*(&self.spi.dr as *const _ as *const vcell::VolatileCell)).get() } 45 | 46 | and 47 | 48 | unsafe { ptr::write_volatile(&self.spi.dr as *const _ as *mut u8, byte) } 49 | 50 | should be replaced with 51 | 52 | unsafe { (*(&self.spi.dr as *const _ as *const vcell::VolatileCell)).set(byte) } 53 | -------------------------------------------------------------------------------- /avatar-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "avatar-cli" 3 | version = "0.1.0" 4 | authors = ["Vadim Kaushan "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | avatar-probe-rs = { path = "../avatar-probe-rs" } 9 | avatar-common = { path = "../avatar-common", features = ["std"] } 10 | -------------------------------------------------------------------------------- /avatar-cli/examples/benchmark.rs: -------------------------------------------------------------------------------- 1 | use avatar_probe_rs::open_probe; 2 | use std::time::{Instant, Duration}; 3 | use std::thread; 4 | 5 | fn main() { 6 | let interface = open_probe(); 7 | 8 | let n = 5000; 9 | 10 | let t0 = Instant::now(); 11 | for _ in 0..n { 12 | let _ = interface.read32(0x2000_0000); 13 | } 14 | let dt = t0.elapsed(); 15 | let uspr = dt.as_micros() / (n as u128); 16 | let rps = 1_000_000 / uspr; 17 | println!("read32: {} us per request, {} requests per second", uspr, rps); 18 | 19 | thread::sleep(Duration::from_millis(200)); 20 | 21 | let t0 = Instant::now(); 22 | for i in 0..n { 23 | let _ = interface.write32(0x2000_0000, i as u32); 24 | } 25 | let dt = t0.elapsed(); 26 | let uspr = dt.as_micros() / (n as u128); 27 | let rps = 1_000_000 / uspr; 28 | println!("write32: {} us per request, {} requests per second", uspr, rps); 29 | 30 | thread::sleep(Duration::from_millis(200)); 31 | 32 | let mut buf = [0; 32]; 33 | let t0 = Instant::now(); 34 | for _ in 0..n/buf.len() { 35 | let _ = interface.read_block32(0x2000_0000, &mut buf); 36 | } 37 | let dt = t0.elapsed(); 38 | let uspr = dt.as_micros() / (n as u128); 39 | let rps = 1_000_000 / uspr; 40 | println!("read_block32: {} us per request, {} requests per second", uspr, rps); 41 | 42 | thread::sleep(Duration::from_millis(200)); 43 | 44 | let buf = [0xdeadbeef; 32]; 45 | let t0 = Instant::now(); 46 | for _ in 0..n/buf.len() { 47 | let _ = interface.write_block32(0x2000_0000, &buf); 48 | } 49 | let dt = t0.elapsed(); 50 | let uspr = dt.as_micros() / (n as u128); 51 | let rps = 1_000_000 / uspr; 52 | println!("write_block32: {} us per request, {} requests per second", uspr, rps); 53 | } 54 | -------------------------------------------------------------------------------- /avatar-cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use avatar_probe_rs::AvatarProbe; 2 | use avatar_common::MemoryInterface; 3 | 4 | fn main() { 5 | let mut interface = match AvatarProbe::open_any() { 6 | Ok(probe) => probe, 7 | Err(e) => { 8 | println!("Can't open probe: {:?}", e); 9 | return; 10 | } 11 | }; 12 | 13 | let mut v = interface.try_read32(0xE004_2000).unwrap(); 14 | if v == 0 { 15 | v = interface.try_read32(0x40015800).unwrap(); 16 | } 17 | println!("IDCODE: {:08x}", v); 18 | } 19 | -------------------------------------------------------------------------------- /avatar-common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "avatar-common" 3 | version = "0.1.0" 4 | authors = ["Vadim Kaushan "] 5 | edition = "2018" 6 | 7 | [features] 8 | std = [] 9 | -------------------------------------------------------------------------------- /avatar-common/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | use core::fmt::Debug; 4 | use core::ops::{Deref, DerefMut}; 5 | 6 | pub trait MemoryInterface { 7 | type Error; 8 | 9 | fn try_read8(&mut self, address: u32) -> Result; 10 | fn try_read16(&mut self, address: u32) -> Result; 11 | fn try_read32(&mut self, address: u32) -> Result; 12 | fn try_read_block32(&mut self, address: u32, data: &mut [u32]) -> Result<(), Self::Error>; 13 | 14 | fn try_write8(&mut self, address: u32, value: u8) -> Result<(), Self::Error>; 15 | fn try_write16(&mut self, address: u32, value: u16) -> Result<(), Self::Error>; 16 | fn try_write32(&mut self, address: u32, value: u32) -> Result<(), Self::Error>; 17 | fn try_write_block32(&mut self, address: u32, data: &[u32]) -> Result<(), Self::Error>; 18 | } 19 | 20 | pub trait ImplementInfallible {} 21 | 22 | pub trait InfallibleMemoryInterface { 23 | fn read8(&mut self, address: u32) -> u8; 24 | fn read16(&mut self, address: u32) -> u16; 25 | fn read32(&mut self, address: u32) -> u32; 26 | fn read_block32(&mut self, address: u32, data: &mut [u32]); 27 | 28 | fn write8(&mut self, address: u32, value: u8); 29 | fn write16(&mut self, address: u32, value: u16); 30 | fn write32(&mut self, address: u32, value: u32); 31 | fn write_block32(&mut self, address: u32, data: &[u32]); 32 | } 33 | 34 | impl InfallibleMemoryInterface for T 35 | where E: Debug, T: MemoryInterface + ImplementInfallible 36 | { 37 | fn read8(&mut self, address: u32) -> u8 { 38 | self.try_read8(address).unwrap() 39 | } 40 | 41 | fn read16(&mut self, address: u32) -> u16 { 42 | self.try_read16(address).unwrap() 43 | } 44 | 45 | fn read32(&mut self, address: u32) -> u32 { 46 | self.try_read32(address).unwrap() 47 | } 48 | 49 | fn read_block32(&mut self, address: u32, data: &mut [u32]) { 50 | self.try_read_block32(address, data).unwrap(); 51 | } 52 | 53 | fn write8(&mut self, address: u32, value: u8) { 54 | self.try_write8(address, value).unwrap() 55 | } 56 | 57 | fn write16(&mut self, address: u32, value: u16) { 58 | self.try_write16(address, value).unwrap() 59 | } 60 | 61 | fn write32(&mut self, address: u32, value: u32) { 62 | self.try_write32(address, value).unwrap() 63 | } 64 | 65 | fn write_block32(&mut self, address: u32, data: &[u32]) { 66 | self.try_write_block32(address, data).unwrap(); 67 | } 68 | } 69 | 70 | pub struct StaticMemoryInterface { 71 | #[cfg(feature = "std")] 72 | pub inner: Box, 73 | #[cfg(not(feature = "std"))] 74 | pub inner: &'static mut dyn InfallibleMemoryInterface, 75 | } 76 | 77 | impl StaticMemoryInterface { 78 | pub fn read(&mut self, address: u32) -> T { 79 | let size = core::mem::size_of::(); 80 | match size { 81 | 1 => unsafe { 82 | let value = self.read8(address); 83 | let ptr = &value as *const u8 as *const T; 84 | ptr.read() 85 | }, 86 | 2 => unsafe { 87 | let value = self.read16(address); 88 | let ptr = &value as *const u16 as *const T; 89 | ptr.read() 90 | }, 91 | 4 => unsafe { 92 | let value = self.read32(address); 93 | let ptr = &value as *const u32 as *const T; 94 | ptr.read() 95 | }, 96 | _ => unimplemented!("storage type is not supported") 97 | } 98 | } 99 | 100 | pub fn write(&mut self, address: u32, value: T) { 101 | let size = core::mem::size_of_val(&value); 102 | 103 | let ptr = &value as *const T as *const (); 104 | match size { 105 | 1 => unsafe { 106 | let value = (ptr as *const u8).read(); 107 | self.write8(address, value) 108 | }, 109 | 2 => unsafe { 110 | let value = (ptr as *const u16).read(); 111 | self.write16(address, value) 112 | }, 113 | 4 => unsafe { 114 | let value = (ptr as *const u32).read(); 115 | self.write32(address, value) 116 | }, 117 | _ => unimplemented!("storage type is not supported") 118 | } 119 | } 120 | } 121 | 122 | impl Deref for StaticMemoryInterface { 123 | type Target = dyn InfallibleMemoryInterface; 124 | 125 | fn deref(&self) -> &Self::Target { 126 | match () { 127 | #[cfg(feature = "std")] 128 | () => self.inner.as_ref(), 129 | #[cfg(not(feature = "std"))] 130 | () => self.inner, 131 | } 132 | } 133 | } 134 | 135 | impl DerefMut for StaticMemoryInterface { 136 | fn deref_mut(&mut self) -> &mut Self::Target { 137 | match () { 138 | #[cfg(feature = "std")] 139 | () => self.inner.as_mut(), 140 | #[cfg(not(feature = "std"))] 141 | () => self.inner, 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /avatar-probe-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "avatar-probe-rs" 3 | version = "0.1.0" 4 | authors = ["Vadim Kaushan "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | probe-rs = "0.10.1" 9 | avatar-common = { path = "../avatar-common", features = ["std"] } 10 | -------------------------------------------------------------------------------- /avatar-probe-rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | use avatar_common::{MemoryInterface, ImplementInfallible, StaticMemoryInterface}; 2 | use probe_rs::{Probe, Error, Session, Core, MemoryInterface as _}; 3 | use std::sync::atomic::{AtomicBool, Ordering}; 4 | use std::time::Duration; 5 | 6 | pub struct AvatarProbe { 7 | _session: Box, 8 | core: Core<'static>, 9 | } 10 | 11 | impl AvatarProbe { 12 | pub fn open_any() -> Result { 13 | let probes = Probe::list_all(); 14 | if probes.len() == 0 { 15 | return Err(Error::UnableToOpenProbe("Probe not found")); 16 | } 17 | 18 | let probe = probes[0].open()?; 19 | 20 | Self::open(probe) 21 | } 22 | 23 | pub fn open(probe: Probe) -> Result { 24 | let session = Box::new(probe.attach("stm32f401")?); 25 | 26 | // This hack is required to store both probe-rs session and 27 | // Core which borrows the session. 28 | let session = Box::leak(session); 29 | let drop_session = unsafe { Box::from_raw(session) }; 30 | 31 | // Select a core. 32 | let mut core = session.core(0)?; 33 | // Reset and halt the attached core. 34 | core.reset_and_halt(Duration::from_millis(500))?; 35 | 36 | Ok(Self{ 37 | _session: drop_session, 38 | core 39 | }) 40 | } 41 | } 42 | 43 | impl MemoryInterface for AvatarProbe { 44 | type Error = Error; 45 | 46 | fn try_read8(&mut self, address: u32) -> Result { 47 | self.core.read_word_8(address) 48 | } 49 | 50 | fn try_read16(&mut self, address: u32) -> Result { 51 | // TODO: fix this 52 | 53 | let value: u32 = self.core.read_word_32(address & !0b11)?; 54 | 55 | let value16 = if address & 0b10 == 0b00 { 56 | (value >> 16) as u16 57 | } else { 58 | (value & 0xffff) as u16 59 | }; 60 | Ok(value16) 61 | } 62 | 63 | fn try_read32(&mut self, address: u32) -> Result { 64 | self.core.read_word_32(address) 65 | } 66 | 67 | fn try_read_block32(&mut self, address: u32, data: &mut [u32]) -> Result<(), Error> { 68 | self.core.read_32(address, data) 69 | } 70 | 71 | fn try_write8(&mut self, address: u32, value: u8) -> Result<(), Error> { 72 | self.core.write_word_8(address, value) 73 | } 74 | 75 | fn try_write16(&mut self, address: u32, value: u16) -> Result<(), Error> { 76 | // TODO: fix this 77 | 78 | let old_value: u32 = self.core.read_word_32(address & !0b11)?; 79 | let new_value = if address & 0b10 == 0b00 { 80 | (old_value & 0xffff_0000) | (value as u32) 81 | } else { 82 | (old_value & 0x0000_ffff) | ((value as u32) << 16) 83 | }; 84 | self.core.write_word_32(address & !0b11, new_value) 85 | } 86 | 87 | fn try_write32(&mut self, address: u32, value: u32) -> Result<(), Error> { 88 | self.core.write_word_32(address, value) 89 | } 90 | 91 | fn try_write_block32(&mut self, address: u32, data: &[u32]) -> Result<(), Error> { 92 | self.core.write_32(address, data) 93 | } 94 | } 95 | 96 | impl ImplementInfallible for AvatarProbe {} 97 | 98 | 99 | pub fn open_probe() -> &'static mut StaticMemoryInterface { 100 | static TAKEN: AtomicBool = AtomicBool::new(false); 101 | 102 | if TAKEN.swap(true, Ordering::SeqCst) { 103 | panic!("Probe is already opened"); 104 | } 105 | 106 | let interface = match AvatarProbe::open_any() { 107 | Ok(probe) => probe, 108 | Err(e) => { 109 | panic!("Can't open probe: {:?}", e); 110 | } 111 | }; 112 | 113 | let interface = Box::new(interface); 114 | 115 | static mut INTERFACE: Option = None; 116 | 117 | unsafe { 118 | INTERFACE.replace(StaticMemoryInterface { 119 | inner: interface 120 | }); 121 | } 122 | 123 | unsafe { &mut INTERFACE }.as_mut().unwrap() 124 | } 125 | -------------------------------------------------------------------------------- /avatar-stm32f103c8/.cargo/config: -------------------------------------------------------------------------------- 1 | [target.thumbv7m-none-eabi] 2 | runner = "arm-none-eabi-gdb -q -x openocd.gdb" 3 | rustflags = [ 4 | "-C", "link-arg=-Tlink.x", 5 | ] 6 | 7 | [build] 8 | target = "thumbv7m-none-eabi" 9 | -------------------------------------------------------------------------------- /avatar-stm32f103c8/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "avatar-stm32f103c8" 3 | version = "0.1.0" 4 | authors = ["Vadim Kaushan "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | cortex-m = "0.6.1" 9 | cortex-m-rt = "0.6.10" 10 | stm32f1xx-hal = { version = "0.5.0", features = ["rt", "stm32-usbd", "stm32f103"] } 11 | embedded-hal = "0.2.3" 12 | panic-semihosting = "0.5.3" 13 | usb-device = "0.2.3" 14 | avatar-usb = { path = "../avatar-usb", default-features = false } 15 | stm32-device-signature = { version = "0.3.0", features = ["stm32f1"] } 16 | -------------------------------------------------------------------------------- /avatar-stm32f103c8/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | use std::path::PathBuf; 4 | 5 | fn main() { 6 | // Put the linker script somewhere the linker can find it 7 | let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); 8 | fs::copy("memory.x", out_dir.join("memory.x")).unwrap(); 9 | println!("cargo:rustc-link-search={}", out_dir.display()); 10 | println!("cargo:rerun-if-changed=memory.x"); 11 | } 12 | -------------------------------------------------------------------------------- /avatar-stm32f103c8/memory.x: -------------------------------------------------------------------------------- 1 | /* STM32F103C8T6 */ 2 | MEMORY 3 | { 4 | FLASH : ORIGIN = 0x08000000, LENGTH = 64K 5 | RAM : ORIGIN = 0x20000000, LENGTH = 20K 6 | } 7 | -------------------------------------------------------------------------------- /avatar-stm32f103c8/openocd.cfg: -------------------------------------------------------------------------------- 1 | source [find interface/cmsis-dap.cfg] 2 | source [find target/stm32f1x.cfg] 3 | targets 4 | # reset halt 5 | # halt 6 | -------------------------------------------------------------------------------- /avatar-stm32f103c8/openocd.gdb: -------------------------------------------------------------------------------- 1 | set confirm off 2 | target extended-remote :3333 3 | set print asm-demangle on 4 | monitor arm semihosting enable 5 | monitor reset halt 6 | load 7 | # monitor verify 8 | # monitor reset 9 | # quit 10 | continue 11 | -------------------------------------------------------------------------------- /avatar-stm32f103c8/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | extern crate panic_semihosting; 5 | 6 | use cortex_m::asm::delay; 7 | use cortex_m_rt::entry; 8 | use stm32f1xx_hal::{prelude::*, stm32}; 9 | use stm32f1xx_hal::usb::{UsbBus, Peripheral}; 10 | use avatar_usb::class::AvatarClass; 11 | use embedded_hal::digital::v2::OutputPin; 12 | use stm32_device_signature::device_id_hex; 13 | 14 | #[entry] 15 | fn main() -> ! { 16 | let dp = stm32::Peripherals::take().unwrap(); 17 | 18 | let mut flash = dp.FLASH.constrain(); 19 | let mut rcc = dp.RCC.constrain(); 20 | 21 | let clocks = rcc 22 | .cfgr 23 | .use_hse(8.mhz()) 24 | .sysclk(48.mhz()) 25 | .pclk1(24.mhz()) 26 | .freeze(&mut flash.acr); 27 | 28 | assert!(clocks.usbclk_valid()); 29 | 30 | let mut gpioa = dp.GPIOA.split(&mut rcc.apb2); 31 | 32 | // BluePill board has a pull-up resistor on the D+ line. 33 | // Pull the D+ pin down to send a RESET condition to the USB bus. 34 | let mut usb_dp = gpioa.pa12.into_push_pull_output(&mut gpioa.crh); 35 | usb_dp.set_low().ok(); 36 | delay(clocks.sysclk().0 / 100); 37 | 38 | let usb = Peripheral { 39 | usb: dp.USB, 40 | pin_dm: gpioa.pa11, 41 | pin_dp: usb_dp.into_floating_input(&mut gpioa.crh), 42 | }; 43 | let usb_bus = UsbBus::new(usb); 44 | 45 | let mut avatar = AvatarClass::new(&usb_bus); 46 | let mut usb_dev = avatar.make_device(&usb_bus, Some(device_id_hex())); 47 | 48 | loop { 49 | usb_dev.poll(&mut [&mut avatar]); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /avatar-usb/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "avatar-usb" 3 | version = "0.1.0" 4 | authors = ["disasm"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | usb-device = "0.2.5" 9 | libusb = { version = "0.3.0", optional = true } 10 | 11 | [features] 12 | std = ["libusb"] 13 | -------------------------------------------------------------------------------- /avatar-usb/src/class.rs: -------------------------------------------------------------------------------- 1 | use usb_device::class_prelude::*; 2 | use usb_device::Result; 3 | use core::marker::PhantomData; 4 | use usb_device::device::{UsbDeviceBuilder, UsbVidPid, UsbDevice}; 5 | use core::convert::TryInto; 6 | 7 | pub const VID: u16 = 0x16c0; 8 | pub const PID: u16 = 0x05dc; 9 | pub const MANUFACTURER: &'static str = "AvatarClass Manufacturer"; 10 | pub const PRODUCT: &'static str = "avatar-rs USB class"; 11 | pub const SERIAL_NUMBER: &'static str = "AvatarClass Serial"; 12 | 13 | pub const REQ_READ8: u8 = 0x01; 14 | pub const REQ_WRITE8: u8 = 0x01; 15 | pub const REQ_READ16: u8 = 0x02; 16 | pub const REQ_WRITE16: u8 = 0x02; 17 | pub const REQ_READ32: u8 = 0x04; 18 | pub const REQ_WRITE32: u8 = 0x04; 19 | 20 | 21 | pub struct AvatarClass<'a, B: UsbBus> { 22 | interface: InterfaceNumber, 23 | read_ep: EndpointOut<'a, B>, 24 | write_ep: EndpointIn<'a, B>, 25 | _marker: PhantomData 26 | } 27 | 28 | impl AvatarClass<'_, B> { 29 | pub fn new(alloc: &UsbBusAllocator) -> AvatarClass { 30 | AvatarClass { 31 | interface: alloc.interface(), 32 | read_ep: alloc.bulk(16), 33 | write_ep: alloc.bulk(16), 34 | _marker: PhantomData 35 | } 36 | } 37 | 38 | /// Convenience method to create a UsbDevice that is configured correctly for AvatarClass. 39 | pub fn make_device<'a, 'b>(&'a self, usb_bus: &'b UsbBusAllocator, serial: Option<&'static str>) -> UsbDevice<'b, B> { 40 | let serial = serial.unwrap_or(SERIAL_NUMBER); 41 | UsbDeviceBuilder::new(&usb_bus, UsbVidPid(VID, PID)) 42 | .manufacturer(MANUFACTURER) 43 | .product(PRODUCT) 44 | .serial_number(serial) 45 | .build() 46 | } 47 | } 48 | 49 | impl UsbClass for AvatarClass<'_, B> { 50 | fn get_configuration_descriptors(&self, writer: &mut DescriptorWriter) -> Result<()> { 51 | writer.interface(self.interface, 0xff, 0, 0)?; 52 | writer.endpoint(&self.write_ep)?; 53 | writer.endpoint(&self.read_ep)?; 54 | Ok(()) 55 | } 56 | 57 | fn endpoint_out(&mut self, addr: EndpointAddress) { 58 | if addr == self.read_ep.address() { 59 | let mut buf = [0; 16]; 60 | let size = self.read_ep.read(&mut buf).unwrap(); 61 | 62 | self.read_ep.stall(); 63 | 64 | if size < 5 { 65 | return; 66 | } 67 | 68 | let command = buf[0]; 69 | let address = u32::from_le_bytes(buf[1..5].try_into().unwrap()) as usize; 70 | 71 | let payload_size = size - 1 - 4; 72 | let payload = &buf[5..]; 73 | 74 | unsafe { 75 | match (payload_size, command) { 76 | (0, REQ_READ8) => { 77 | let value = (address as * const u8).read_volatile(); 78 | self.write_ep.write(&[value]).ok(); 79 | } 80 | (0, REQ_READ16) => { 81 | let value = (address as *const u16).read_volatile(); 82 | self.write_ep.write(&value.to_le_bytes()).ok(); 83 | } 84 | (0, REQ_READ32) => { 85 | let value = (address as *const u32).read_volatile(); 86 | self.write_ep.write(&value.to_le_bytes()).ok(); 87 | } 88 | (1, REQ_WRITE8) => { 89 | (address as *mut u8).write_volatile(payload[0]); 90 | self.read_ep.unstall(); 91 | } 92 | (2, REQ_WRITE16) => { 93 | let value = u16::from_le_bytes(payload[..2].try_into().unwrap()); 94 | (address as *mut u16).write_volatile(value); 95 | self.read_ep.unstall(); 96 | } 97 | (4, REQ_WRITE32) => { 98 | let value = u32::from_le_bytes(payload[..4].try_into().unwrap()); 99 | (address as *mut u32).write_volatile(value); 100 | self.read_ep.unstall(); 101 | } 102 | _ => return, 103 | } 104 | } 105 | } 106 | } 107 | 108 | fn endpoint_in_complete(&mut self, addr: EndpointAddress) { 109 | if addr == self.write_ep.address() { 110 | self.read_ep.unstall(); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /avatar-usb/src/host.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | use libusb::*; 3 | use crate::class; 4 | 5 | pub const TIMEOUT: Duration = Duration::from_secs(1); 6 | pub const EN_US: u16 = 0x0409; 7 | 8 | 9 | pub struct AvatarDevice<'a> { 10 | pub handle: DeviceHandle<'a>, 11 | } 12 | 13 | impl AvatarDevice<'_> { 14 | pub fn open(ctx: &Context) -> Result> { 15 | for device in ctx.devices()?.iter() { 16 | let device_descriptor = device.device_descriptor()?; 17 | 18 | if !(device_descriptor.vendor_id() == class::VID 19 | && device_descriptor.product_id() == class::PID) { 20 | continue; 21 | } 22 | 23 | let handle = device.open()?; 24 | 25 | let languages = handle.read_languages(TIMEOUT)?; 26 | if languages.len() == 0 || languages[0].lang_id() != EN_US { 27 | continue; 28 | } 29 | 30 | let product = handle.read_product_string(languages[0], &device_descriptor, TIMEOUT)?; 31 | if product == class::PRODUCT { 32 | return Ok(AvatarDevice { 33 | handle, 34 | }); 35 | } 36 | } 37 | 38 | Err(libusb::Error::NoDevice) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /avatar-usb/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | pub mod class; 4 | 5 | #[cfg(feature = "std")] 6 | pub mod host; 7 | -------------------------------------------------------------------------------- /examples-blue-pill/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples-blue-pill" 3 | version = "0.1.0" 4 | authors = ["Vadim Kaushan "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | avatar-probe-rs = { path = "../avatar-probe-rs" } 9 | vcell = "0.1.2" 10 | cortex-m = "0.6.2" 11 | stm32f1xx-hal = { version = "0.5.3", features = ["stm32f103"] } 12 | embedded-hal = "0.2.3" 13 | -------------------------------------------------------------------------------- /examples-blue-pill/examples/blinky.rs: -------------------------------------------------------------------------------- 1 | // Target board: Blue Pill 2 | 3 | use avatar_probe_rs::open_probe; 4 | use stm32f1xx_hal::prelude::*; 5 | use stm32f1xx_hal::stm32; 6 | use embedded_hal::digital::v2::OutputPin; 7 | use std::time::Duration; 8 | use std::thread; 9 | 10 | 11 | fn main() { 12 | let interface = open_probe(); 13 | vcell::set_memory_interface(interface); 14 | 15 | let dp = stm32::Peripherals::take().unwrap(); 16 | 17 | let mut flash = dp.FLASH.constrain(); 18 | let mut rcc = dp.RCC.constrain(); 19 | 20 | let _clocks = rcc.cfgr.freeze(&mut flash.acr); 21 | 22 | let mut gpioc = dp.GPIOC.split(&mut rcc.apb2); 23 | let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh); 24 | led.set_high().ok(); 25 | 26 | loop { 27 | led.set_low().ok(); 28 | thread::sleep(Duration::from_millis(500)); 29 | led.set_high().ok(); 30 | thread::sleep(Duration::from_millis(500)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples-blue-pill/src/lib.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Disasm/avatar-rs/799082f09f79a37c9d8d5776505c46600b5bd4ff/examples-blue-pill/src/lib.rs -------------------------------------------------------------------------------- /examples-dwm1001-dev/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples-dwm1001-dev" 3 | version = "0.1.0" 4 | authors = ["Vadim Kaushan "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | avatar-probe-rs = { path = "../avatar-probe-rs" } 9 | vcell = "0.1.2" 10 | nrf52832-hal = "0.8.1" 11 | embedded-hal = "0.2.3" 12 | -------------------------------------------------------------------------------- /examples-dwm1001-dev/examples/blinky.rs: -------------------------------------------------------------------------------- 1 | // Target board: DWM1001-DEV 2 | 3 | use avatar_probe_rs::open_probe; 4 | use nrf52832_hal::nrf52832_pac as pac; 5 | use nrf52832_hal::gpio; 6 | use nrf52832_hal::gpio::p0::*; 7 | use nrf52832_hal::gpio::Level; 8 | use nrf52832_hal::gpio::*; 9 | use embedded_hal::digital::v2::OutputPin; 10 | use std::time::Duration; 11 | use std::thread; 12 | 13 | 14 | fn main() { 15 | let interface = open_probe(); 16 | vcell::set_memory_interface(interface); 17 | 18 | let p = pac::Peripherals::take().unwrap(); 19 | let port0 = p.P0.split(); 20 | 21 | let mut led1: P0_30> = port0.p0_30.into_push_pull_output(Level::High); 22 | 23 | loop { 24 | led1.set_high().ok(); 25 | thread::sleep(Duration::from_millis(100)); 26 | led1.set_low().ok(); 27 | thread::sleep(Duration::from_millis(100)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples-dwm1001-dev/src/lib.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Disasm/avatar-rs/799082f09f79a37c9d8d5776505c46600b5bd4ff/examples-dwm1001-dev/src/lib.rs -------------------------------------------------------------------------------- /examples-nucleo-f042k6/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples-nucleo-f042k6" 3 | version = "0.1.0" 4 | authors = ["Vadim Kaushan "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | avatar-probe-rs = { path = "../avatar-probe-rs" } 9 | vcell = "0.1.2" 10 | cortex-m = "0.6.2" 11 | stm32f0xx-hal = { version = "0.16.0", features = ["stm32f042"] } 12 | bme280 = "0.2.1" 13 | linux-embedded-hal = "0.3.0" 14 | -------------------------------------------------------------------------------- /examples-nucleo-f042k6/examples/blinky.rs: -------------------------------------------------------------------------------- 1 | // Target board: NUCLEO-F042K6 2 | 3 | use avatar_probe_rs::open_probe; 4 | use cortex_m::interrupt; 5 | use stm32f0xx_hal::prelude::*; 6 | use stm32f0xx_hal::stm32; 7 | use std::time::Duration; 8 | use std::thread; 9 | 10 | 11 | fn main() { 12 | let interface = open_probe(); 13 | vcell::set_memory_interface(interface); 14 | 15 | println!("Staring blinkey!"); 16 | let mut dp = stm32::Peripherals::take().unwrap(); 17 | let mut rcc = dp.RCC.configure().freeze(&mut dp.FLASH); 18 | let gpiob = dp.GPIOB.split(&mut rcc); 19 | 20 | let mut led = interrupt::free(|cs| { 21 | gpiob.pb3.into_push_pull_output(cs) 22 | }); 23 | 24 | loop { 25 | led.set_high().ok(); 26 | thread::sleep(Duration::from_millis(500)); 27 | led.set_low().ok(); 28 | thread::sleep(Duration::from_millis(500)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples-nucleo-f042k6/examples/i2c_bme280.rs: -------------------------------------------------------------------------------- 1 | // Target board: NUCLEO-F042K6 2 | // 3 | // Connections: 4 | // D4 - SDA 5 | // D5 - SCL 6 | 7 | use avatar_probe_rs::open_probe; 8 | use cortex_m::interrupt; 9 | use stm32f0xx_hal::prelude::*; 10 | use stm32f0xx_hal::stm32; 11 | use stm32f0xx_hal::i2c::I2c; 12 | use std::time::Duration; 13 | use std::thread; 14 | use bme280::BME280; 15 | use linux_embedded_hal::Delay; 16 | 17 | 18 | fn main() { 19 | let interface = open_probe(); 20 | vcell::set_memory_interface(interface); 21 | 22 | let mut p = stm32::Peripherals::take().unwrap(); 23 | 24 | interrupt::free(|cs| { 25 | let mut rcc = p.RCC.configure().freeze(&mut p.FLASH); 26 | 27 | let gpiob = p.GPIOB.split(&mut rcc); 28 | 29 | // Configure pins for I2C 30 | let sda = gpiob.pb7.into_alternate_af1(cs); 31 | let scl = gpiob.pb6.into_alternate_af1(cs); 32 | 33 | // Configure I2C with 100kHz rate 34 | let i2c = I2c::i2c1(p.I2C1, (scl, sda), 100.khz(), &mut rcc); 35 | 36 | let mut bme280 = BME280::new_primary(i2c, Delay); 37 | 38 | // initialize the sensor 39 | bme280.init().unwrap(); 40 | 41 | loop { 42 | // measure temperature, pressure, and humidity 43 | let measurements = bme280.measure().unwrap(); 44 | 45 | println!(); 46 | println!("Relative Humidity = {}%", measurements.humidity); 47 | println!("Temperature = {} deg C", measurements.temperature); 48 | println!("Pressure = {} pascals", measurements.pressure); 49 | 50 | thread::sleep(Duration::from_millis(1000)); 51 | } 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /examples-nucleo-f042k6/examples/serial_hello.rs: -------------------------------------------------------------------------------- 1 | // Target board: NUCLEO-F042K6 2 | // 3 | // Serial is connected to the on-board ST-LINK 4 | 5 | use avatar_probe_rs::open_probe; 6 | use cortex_m::interrupt; 7 | use stm32f0xx_hal::prelude::*; 8 | use stm32f0xx_hal::stm32; 9 | use stm32f0xx_hal::serial::Serial; 10 | use core::fmt::Write; 11 | use std::time::Duration; 12 | use std::thread; 13 | use stm32f0xx_hal::rcc::HSEBypassMode; 14 | 15 | 16 | fn main() { 17 | let interface = open_probe(); 18 | vcell::set_memory_interface(interface); 19 | 20 | let mut dp = stm32::Peripherals::take().unwrap(); 21 | 22 | interrupt::free(|cs| { 23 | let mut rcc = dp.RCC.configure().hse(8.mhz(), HSEBypassMode::Bypassed).sysclk(8.mhz()).freeze(&mut dp.FLASH); 24 | 25 | let gpioa = dp.GPIOA.split(&mut rcc); 26 | let tx = gpioa.pa2.into_alternate_af1(cs); 27 | let rx = gpioa.pa15.into_alternate_af1(cs); 28 | 29 | let mut serial = Serial::usart2(dp.USART2, (tx, rx), 115_200.bps(), &mut rcc); 30 | 31 | write!(serial, "Hello, world!\r\n").unwrap(); 32 | 33 | loop { 34 | thread::sleep(Duration::from_millis(500)); 35 | write!(serial, "Hello again!\r\n").unwrap(); 36 | } 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /examples-nucleo-f042k6/examples/test_hpdl.rs: -------------------------------------------------------------------------------- 1 | // Target board: NUCLEO-F042K6 2 | 3 | use avatar_probe_rs::open_probe; 4 | use cortex_m::interrupt; 5 | use stm32f0xx_hal::prelude::*; 6 | use stm32f0xx_hal::stm32; 7 | use std::time::Duration; 8 | use std::thread; 9 | use stm32f0xx_hal::gpio::{ 10 | Output, PushPull, 11 | gpioa::*, 12 | gpiob::*, 13 | }; 14 | 15 | macro_rules! pin_set { 16 | ($value:expr, $mask:expr, $pin:expr) => { 17 | if ($value & $mask) != 0 { 18 | $pin.set_high().ok(); 19 | } else { 20 | $pin.set_low().ok(); 21 | } 22 | } 23 | } 24 | 25 | struct HPDL { 26 | d0: PA3>, // A2 27 | d1: PA4>, // A3 28 | d2: PA5>, // A4 29 | d3: PA6>, // A5 30 | d4: PA8>, // D9 31 | d5: PB1>, // D6 32 | d6: PA7>, // A6 33 | a0: PB4>, // D12 34 | a1: PB5>, // D11 35 | nwr: PA11>, // D10 36 | } 37 | 38 | impl HPDL { 39 | pub fn init(&mut self) { 40 | self.nwr.set_high(); 41 | } 42 | 43 | pub fn write(&mut self, addr: u8, byte: u8) { 44 | pin_set!(addr, 0b01, self.a0); 45 | pin_set!(addr, 0b10, self.a1); 46 | 47 | pin_set!(byte, 0b0000001, self.d0); 48 | pin_set!(byte, 0b0000010, self.d1); 49 | pin_set!(byte, 0b0000100, self.d2); 50 | pin_set!(byte, 0b0001000, self.d3); 51 | pin_set!(byte, 0b0010000, self.d4); 52 | pin_set!(byte, 0b0100000, self.d5); 53 | pin_set!(byte, 0b1000000, self.d6); 54 | 55 | self.nwr.set_low().ok(); 56 | self.nwr.set_high().ok(); 57 | } 58 | 59 | pub fn write_str(&mut self, s: &str) { 60 | let s = s.to_ascii_uppercase(); 61 | let bytes = s.as_bytes(); 62 | 63 | for i in 0..4 { 64 | if i < bytes.len() { 65 | self.write(3 - i as u8, bytes[i]); 66 | } else { 67 | self.write(3 - i as u8, 0); 68 | } 69 | } 70 | } 71 | } 72 | 73 | fn main() { 74 | let interface = open_probe(); 75 | vcell::set_memory_interface(interface); 76 | 77 | let mut dp = stm32::Peripherals::take().unwrap(); 78 | let mut rcc = dp.RCC.configure().freeze(&mut dp.FLASH); 79 | 80 | let gpioa = dp.GPIOA.split(&mut rcc); 81 | let gpiob = dp.GPIOB.split(&mut rcc); 82 | 83 | let cs = unsafe { core::mem::zeroed() }; 84 | let mut hpdl = HPDL { 85 | d5: gpiob.pb1.into_push_pull_output(&cs), // D6 86 | d4: gpioa.pa8.into_push_pull_output(&cs), // D9 87 | nwr: gpioa.pa11.into_push_pull_output(&cs), // D10 88 | a1: gpiob.pb5.into_push_pull_output(&cs), // D11 89 | a0: gpiob.pb4.into_push_pull_output(&cs), // D12 90 | 91 | d6: gpioa.pa7.into_push_pull_output(&cs), // A6 92 | d3: gpioa.pa6.into_push_pull_output(&cs), // A5 93 | d2: gpioa.pa5.into_push_pull_output(&cs), // A4 94 | d1: gpioa.pa4.into_push_pull_output(&cs), // A3 95 | d0: gpioa.pa3.into_push_pull_output(&cs), // A2 96 | }; 97 | hpdl.init(); 98 | 99 | let args: Vec = std::env::args().collect(); 100 | let s = " ".to_string() + &args.get(1).unwrap_or(&"test test".to_string()); 101 | 102 | let mut offset = 0; 103 | loop { 104 | hpdl.write_str(&s[offset..]); 105 | offset = offset + 1; 106 | if offset >= s.len() { 107 | offset = 0; 108 | } 109 | 110 | thread::sleep(Duration::from_millis(200)); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /examples-nucleo-f042k6/src/lib.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Disasm/avatar-rs/799082f09f79a37c9d8d5776505c46600b5bd4ff/examples-nucleo-f042k6/src/lib.rs --------------------------------------------------------------------------------