diff --git a/.vscode/settings.json b/.vscode/settings.json index 9d12d6f..1b1eb88 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,4 +8,5 @@ "rust-analyzer.linkedProjects": [ "./Cargo.toml" ], + "rust-analyzer.showUnlinkedFileNotification": false, } \ No newline at end of file diff --git a/build.rs b/build.rs index 9542b13..5ede928 100644 --- a/build.rs +++ b/build.rs @@ -119,7 +119,7 @@ fn create_config_struct(content: &str, path: &str, struct_name: &str) { fn main() { create_config_struct( include_str!("mouse.conf"), - "src/mouse_config.rs", + "src/mouse_hid/mouse_config.rs", "MouseConfig", ); } diff --git a/src/main.rs b/src/main.rs index bf17457..d0e268d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,9 +2,10 @@ #![no_main] #![feature(type_alias_impl_trait)] #![feature(never_type)] +#![feature(async_closure)] -mod mouse_config; mod mouse_hid; +mod mouse_sensor; use embassy_executor::Spawner; use embassy_futures::join::join; @@ -12,7 +13,11 @@ use embassy_time::{Duration, Timer}; use usbd_hid::descriptor::MouseReport; -use crate::{mouse_config::MouseConfig, mouse_hid::MouseHID}; +use crate::{ + mouse_hid::mouse_config::MouseConfig, + mouse_hid::MouseHID, + mouse_sensor::{MouseSensor, MouseSensorPins}, +}; use {defmt_rtt as _, panic_probe as _}; @@ -21,6 +26,12 @@ async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); let mut mouse_hid = MouseHID::new(p.USB, MouseConfig::new()); + let mut mouse_sensor = MouseSensor::new( + p.PIN_25, + MouseSensorPins::new(p.SPI0, p.PIN_2, p.PIN_3, p.PIN_4, p.PIN_5), + |_| (), + ) + .await; let usb_fut = MouseHID::run_usb().await; diff --git a/src/mouse_hid.rs b/src/mouse_hid/mod.rs similarity index 98% rename from src/mouse_hid.rs rename to src/mouse_hid/mod.rs index d505efc..adf1731 100644 --- a/src/mouse_hid.rs +++ b/src/mouse_hid/mod.rs @@ -1,3 +1,5 @@ +pub mod mouse_config; + use core::future::Future; use embassy_rp::usb::Driver; @@ -9,7 +11,7 @@ use embassy_usb::{Builder, Config, UsbDevice}; use usbd_hid::descriptor::{MouseReport, SerializedDescriptor}; -use crate::mouse_config::MouseConfig; +use mouse_config::MouseConfig; static mut DEVICE_DESCRIPTOR_BUFFER: [u8; 256] = [0; 256]; static mut CONFIG_DESCRIPTOR_BUFFER: [u8; 256] = [0; 256]; diff --git a/src/mouse_hid/mouse_config.rs b/src/mouse_hid/mouse_config.rs new file mode 100644 index 0000000..01ad942 --- /dev/null +++ b/src/mouse_hid/mouse_config.rs @@ -0,0 +1,19 @@ +pub struct MouseConfig<'a> { + pub vendor_id: i32, + pub product_id: i32, + pub manufacturer: &'a str, + pub product: &'a str, + pub serial_number: &'a str, +} + +impl<'a> MouseConfig<'a> { + pub const fn new() -> Self { + Self { + vendor_id: 0x046d, + product_id: 0x101b, + manufacturer: "Logitech", + product: "Performance Plus M705", + serial_number: "B14D65DA", + } + } +} diff --git a/src/mouse_sensor/mod.rs b/src/mouse_sensor/mod.rs new file mode 100644 index 0000000..12a4488 --- /dev/null +++ b/src/mouse_sensor/mod.rs @@ -0,0 +1,4 @@ +mod paw3212db_tjdt; +mod wrapper; + +pub use wrapper::{MouseSensor, MouseSensorPins}; diff --git a/src/mouse_sensor/paw3212db_tjdt.rs b/src/mouse_sensor/paw3212db_tjdt.rs new file mode 100644 index 0000000..c6550d2 --- /dev/null +++ b/src/mouse_sensor/paw3212db_tjdt.rs @@ -0,0 +1,159 @@ +use core::mem; + +use embassy_rp::{ + gpio::{Level, Output}, + spi::{ + Blocking, ClkPin, Config, CsPin, Instance as SpiInstance, MisoPin, MosiPin, Polarity, Spi, + }, + Peripheral, +}; +use embassy_time::{Duration, Timer}; + +#[allow(non_camel_case_types)] +pub struct PAW3212DB_TJDT<'a, L, CS, T> +where + L: FnMut(&str), + CS: CsPin + 'a, + T: SpiInstance, +{ + cs: Output<'a, CS>, + spi: Spi<'a, T, Blocking>, + + pub log: L, +} + +impl<'a, L, CS, T> PAW3212DB_TJDT<'a, L, CS, T> +where + L: FnMut(&str), + CS: CsPin + 'a, + T: SpiInstance, +{ + const READ: u8 = 0b0111_1111; + const WRITE: u8 = 0b1000_0000; + + const REG_PRODUCT_ID1: u8 = 0x00; + const REG_PRODUCT_ID2: u8 = 0x01; + const REG_MOTION_STATUS: u8 = 0x02; + const REG_DELTA_X: u8 = 0x03; + const REG_DELTA_Y: u8 = 0x04; + const REG_OPERATION_MODE: u8 = 0x05; + const REG_CONFIGURATION: u8 = 0x06; + const REG_WRITE_PROTECT: u8 = 0x09; + const REG_SLEEP1: u8 = 0x0A; + const REG_SLEEP2: u8 = 0x0B; + const REG_SLEEP3: u8 = 0x0C; + const REG_CPI_X: u8 = 0x0D; + const REG_CPI_Y: u8 = 0x0E; + const REG_DELTA_XY_HI: u8 = 0x12; + const REG_IQC: u8 = 0x13; + const REG_SHUTTER: u8 = 0x14; + const REG_FRAME_AVG: u8 = 0x17; + const REG_MOUSE_OPTION: u8 = 0x19; + const REG_SPI_MODE: u8 = 0x26; + + pub async fn new( + spi: impl Peripheral

+ 'a, + clk: impl Peripheral

+ 'a> + 'a, + mosi: impl Peripheral

+ 'a> + 'a, + miso: impl Peripheral

+ 'a> + 'a, + cs: impl Peripheral

+ 'a, + log: L, + ) -> PAW3212DB_TJDT<'a, L, CS, T> { + let mut spi_config = Config::default(); + spi_config.frequency = 2_000_000; + spi_config.polarity = Polarity::IdleHigh; + + let spi = Spi::new_blocking(spi, clk, mosi, miso, spi_config); + + let mut cs = Output::new(cs, Level::Low); + + cs.set_low(); + + Timer::after(Duration::from_millis(2)).await; + + cs.set_high(); + + Self { cs, spi, log } + } + + fn access(&mut self, f: F) -> R + where + F: Fn(&mut Self) -> R, + { + self.cs.set_low(); + Timer::after(Duration::from_micros(1)); + + let res = f(self); + + self.cs.set_high(); + Timer::after(Duration::from_micros(2)); + + res + } + + fn read(&mut self, address: u8) -> u8 { + loop { + let res = self.access(move |me| { + if me.spi.blocking_write(&[address & Self::READ, 0]).is_err() { + (me.log)("send address (read) failed"); + } + + let mut buf = [0]; + + if me.spi.blocking_read(&mut buf).is_err() { + (me.log)("read failed"); + return 0; + } + + buf[0] << 1 + }); + + if res != address { + return res; + } + } + } + + fn write(&mut self, address: u8, value: u8) { + self.access(move |me| { + if me + .spi + .blocking_write(&[address | Self::WRITE, value]) + .is_err() + { + (me.log)("send address (write) failed"); + } + }); + } +} + +impl<'a, L, CS, T> PAW3212DB_TJDT<'a, L, CS, T> +where + L: FnMut(&str), + CS: CsPin + 'a, + T: SpiInstance, +{ + pub fn delta_x(&mut self) -> i8 { + unsafe { mem::transmute(self.read(Self::REG_DELTA_X)) } + } + + pub fn delta_y(&mut self) -> i8 { + unsafe { mem::transmute(self.read(Self::REG_DELTA_Y)) } + } + + pub fn product_id_1(&mut self) -> u8 { + self.read(Self::REG_PRODUCT_ID1) + } + + pub fn product_id_2(&mut self) -> u8 { + self.read(Self::REG_PRODUCT_ID2) + } + + pub fn operation_mode(&mut self) -> u8 { + self.read(Self::REG_OPERATION_MODE) + } + + pub fn motion_status(&mut self) -> u8 { + self.read(Self::REG_MOTION_STATUS) + } +} diff --git a/src/mouse_sensor/wrapper.rs b/src/mouse_sensor/wrapper.rs new file mode 100644 index 0000000..48dab7c --- /dev/null +++ b/src/mouse_sensor/wrapper.rs @@ -0,0 +1,169 @@ +use super::paw3212db_tjdt::PAW3212DB_TJDT; + +use embassy_rp::{ + gpio::{Level, Output}, + peripherals::PIN_25, + spi::{ClkPin, CsPin, Instance, MisoPin, MosiPin}, + Peripheral, +}; + +use core::marker::PhantomData; + +pub struct MouseSensorPins<'a, T, Clk, Mosi, Miso, CSP, P, C, MI, MO, CS> +where + T: Instance, + Clk: ClkPin + 'a, + Mosi: MosiPin + 'a, + Miso: MisoPin + 'a, + CSP: CsPin + 'a, + + P: Peripheral

+ 'a, + C: Peripheral

+ 'a, + MI: Peripheral

+ 'a, + MO: Peripheral

+ 'a, + CS: Peripheral

+ 'a, +{ + spi: P, + clk: C, + mosi: MI, + miso: MO, + cs: CS, + + d: PhantomData<&'a ()>, +} + +impl<'a, T, Clk, Mosi, Miso, CSP, P, C, MI, MO, CS> + MouseSensorPins<'a, T, Clk, Mosi, Miso, CSP, P, C, MI, MO, CS> +where + T: Instance, + Clk: ClkPin + 'a, + Mosi: MosiPin + 'a, + Miso: MisoPin + 'a, + CSP: CsPin + 'a, + + P: Peripheral

+ 'a, + C: Peripheral

+ 'a, + MI: Peripheral

+ 'a, + MO: Peripheral

+ 'a, + CS: Peripheral

+ 'a, +{ + pub fn new(spi: P, clk: C, mosi: MI, miso: MO, cs: CS) -> Self { + Self { + spi, + clk, + mosi, + miso, + cs, + + d: PhantomData, + } + } +} + +pub struct MouseSensor<'d, L, T, CSP> +where + L: FnMut(&str), + T: Instance, + CSP: CsPin + 'd, +{ + spi_device: PAW3212DB_TJDT<'d, L, CSP, T>, + + led: Output<'d, PIN_25>, +} + +impl<'d, L, T, CSP> MouseSensor<'d, L, T, CSP> +where + L: FnMut(&str) + 'd, + T: Instance, + CSP: CsPin + 'd, +{ + pub async fn new<'a, Clk, Mosi, Miso, P, C, MI, MO, CS>( + led: PIN_25, + sensor_pins: MouseSensorPins<'d, T, Clk, Mosi, Miso, CSP, P, C, MI, MO, CS>, + log: L, + ) -> MouseSensor<'d, L, T, CSP> + where + Clk: ClkPin + 'a, + Mosi: MosiPin + 'a, + Miso: MisoPin + 'a, + + P: Peripheral

+ 'a, + C: Peripheral

+ 'a, + MI: Peripheral

+ 'a, + MO: Peripheral

+ 'a, + CS: Peripheral

+ 'a, + { + // These are implicitly used by the spi driver if they are in the correct mode + + let led = Output::new(led, Level::Low); + let spi_device = PAW3212DB_TJDT::new( + sensor_pins.spi, + sensor_pins.clk, + sensor_pins.mosi, + sensor_pins.miso, + sensor_pins.cs, + log, + ) + .await; + + let mut me = Self { spi_device, led }; + + me.led.set_high(); + + // verify initialization + loop { + if me.verify_product_id_1() { + break; + } + } + + loop { + if me.verify_product_id_2() { + break; + } + } + + me.led.set_low(); + + me + } + + fn verify_product_id_1(&mut self) -> bool { + const DEFAULT_VALUE: u8 = 0x30; + + DEFAULT_VALUE == self.spi_device.product_id_1() + } + + fn verify_product_id_2(&mut self) -> bool { + const DEFAULT_VALUE: u8 = 0x02; + + DEFAULT_VALUE == self.spi_device.product_id_2() + } + + fn motion_detected(&mut self) -> (bool, bool, bool) { + let reg = self.spi_device.motion_status(); + + let motion = (reg & 0b10000000) != 0; + let x_overflow = (reg & 0b00010000) != 0; + let y_overflow = (reg & 0b00001000) != 0; + + (motion, x_overflow, y_overflow) + } + + pub fn read_movement(&'d mut self) -> Option<(i8, i8)> { + let (motion, x_overflow, y_overflow) = self.motion_detected(); + + if motion { + if x_overflow || y_overflow { + return None; + } + + let x = self.spi_device.delta_x(); + let y = self.spi_device.delta_y(); + + Some((x, y)) + } else { + None + } + } +}