Add first mouse sensor implementation
This commit is contained in:
parent
dc62866577
commit
73f246e7d3
8 changed files with 369 additions and 4 deletions
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -8,4 +8,5 @@
|
||||||
"rust-analyzer.linkedProjects": [
|
"rust-analyzer.linkedProjects": [
|
||||||
"./Cargo.toml"
|
"./Cargo.toml"
|
||||||
],
|
],
|
||||||
|
"rust-analyzer.showUnlinkedFileNotification": false,
|
||||||
}
|
}
|
2
build.rs
2
build.rs
|
@ -119,7 +119,7 @@ fn create_config_struct(content: &str, path: &str, struct_name: &str) {
|
||||||
fn main() {
|
fn main() {
|
||||||
create_config_struct(
|
create_config_struct(
|
||||||
include_str!("mouse.conf"),
|
include_str!("mouse.conf"),
|
||||||
"src/mouse_config.rs",
|
"src/mouse_hid/mouse_config.rs",
|
||||||
"MouseConfig",
|
"MouseConfig",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
15
src/main.rs
15
src/main.rs
|
@ -2,9 +2,10 @@
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![feature(type_alias_impl_trait)]
|
#![feature(type_alias_impl_trait)]
|
||||||
#![feature(never_type)]
|
#![feature(never_type)]
|
||||||
|
#![feature(async_closure)]
|
||||||
|
|
||||||
mod mouse_config;
|
|
||||||
mod mouse_hid;
|
mod mouse_hid;
|
||||||
|
mod mouse_sensor;
|
||||||
|
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_futures::join::join;
|
use embassy_futures::join::join;
|
||||||
|
@ -12,7 +13,11 @@ use embassy_time::{Duration, Timer};
|
||||||
|
|
||||||
use usbd_hid::descriptor::MouseReport;
|
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 _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
|
@ -21,6 +26,12 @@ async fn main(_spawner: Spawner) {
|
||||||
let p = embassy_rp::init(Default::default());
|
let p = embassy_rp::init(Default::default());
|
||||||
|
|
||||||
let mut mouse_hid = MouseHID::new(p.USB, MouseConfig::new());
|
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;
|
let usb_fut = MouseHID::run_usb().await;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
pub mod mouse_config;
|
||||||
|
|
||||||
use core::future::Future;
|
use core::future::Future;
|
||||||
|
|
||||||
use embassy_rp::usb::Driver;
|
use embassy_rp::usb::Driver;
|
||||||
|
@ -9,7 +11,7 @@ use embassy_usb::{Builder, Config, UsbDevice};
|
||||||
|
|
||||||
use usbd_hid::descriptor::{MouseReport, SerializedDescriptor};
|
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 DEVICE_DESCRIPTOR_BUFFER: [u8; 256] = [0; 256];
|
||||||
static mut CONFIG_DESCRIPTOR_BUFFER: [u8; 256] = [0; 256];
|
static mut CONFIG_DESCRIPTOR_BUFFER: [u8; 256] = [0; 256];
|
19
src/mouse_hid/mouse_config.rs
Normal file
19
src/mouse_hid/mouse_config.rs
Normal file
|
@ -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",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
src/mouse_sensor/mod.rs
Normal file
4
src/mouse_sensor/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
mod paw3212db_tjdt;
|
||||||
|
mod wrapper;
|
||||||
|
|
||||||
|
pub use wrapper::{MouseSensor, MouseSensorPins};
|
159
src/mouse_sensor/paw3212db_tjdt.rs
Normal file
159
src/mouse_sensor/paw3212db_tjdt.rs
Normal file
|
@ -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<T> + '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<T> + '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<P = T> + 'a,
|
||||||
|
clk: impl Peripheral<P = impl ClkPin<T> + 'a> + 'a,
|
||||||
|
mosi: impl Peripheral<P = impl MosiPin<T> + 'a> + 'a,
|
||||||
|
miso: impl Peripheral<P = impl MisoPin<T> + 'a> + 'a,
|
||||||
|
cs: impl Peripheral<P = CS> + '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<F, R>(&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<T> + '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)
|
||||||
|
}
|
||||||
|
}
|
169
src/mouse_sensor/wrapper.rs
Normal file
169
src/mouse_sensor/wrapper.rs
Normal file
|
@ -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<T> + 'a,
|
||||||
|
Mosi: MosiPin<T> + 'a,
|
||||||
|
Miso: MisoPin<T> + 'a,
|
||||||
|
CSP: CsPin<T> + 'a,
|
||||||
|
|
||||||
|
P: Peripheral<P = T> + 'a,
|
||||||
|
C: Peripheral<P = Clk> + 'a,
|
||||||
|
MI: Peripheral<P = Mosi> + 'a,
|
||||||
|
MO: Peripheral<P = Miso> + 'a,
|
||||||
|
CS: Peripheral<P = CSP> + '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<T> + 'a,
|
||||||
|
Mosi: MosiPin<T> + 'a,
|
||||||
|
Miso: MisoPin<T> + 'a,
|
||||||
|
CSP: CsPin<T> + 'a,
|
||||||
|
|
||||||
|
P: Peripheral<P = T> + 'a,
|
||||||
|
C: Peripheral<P = Clk> + 'a,
|
||||||
|
MI: Peripheral<P = Mosi> + 'a,
|
||||||
|
MO: Peripheral<P = Miso> + 'a,
|
||||||
|
CS: Peripheral<P = CSP> + '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<T> + '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<T> + '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<T> + 'a,
|
||||||
|
Mosi: MosiPin<T> + 'a,
|
||||||
|
Miso: MisoPin<T> + 'a,
|
||||||
|
|
||||||
|
P: Peripheral<P = T> + 'a,
|
||||||
|
C: Peripheral<P = Clk> + 'a,
|
||||||
|
MI: Peripheral<P = Mosi> + 'a,
|
||||||
|
MO: Peripheral<P = Miso> + 'a,
|
||||||
|
CS: Peripheral<P = CSP> + '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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue