From a419d7eef99c917ceebab1d67a9ea1791775d135 Mon Sep 17 00:00:00 2001 From: Per Lindgren <per.lindgren@ltu.se> Date: Wed, 23 Dec 2020 23:55:58 +0100 Subject: [PATCH] pmw3389 wip --- examples/pmw3389.rs | 369 ++++++++++++++++++++++++++++++++++++++++ examples/rtic_blinky.rs | 67 ++++++++ 2 files changed, 436 insertions(+) create mode 100644 examples/pmw3389.rs create mode 100644 examples/rtic_blinky.rs diff --git a/examples/pmw3389.rs b/examples/pmw3389.rs new file mode 100644 index 0000000..b1d0c3f --- /dev/null +++ b/examples/pmw3389.rs @@ -0,0 +1,369 @@ +#![deny(unsafe_code)] +// #![deny(warnings)] +#![no_main] +#![no_std] + +use cortex_m::{ + iprintln, + peripheral::{itm::Stim, DWT}, +}; +use embedded_hal::spi::MODE_0; +// use cortex_m_semihosting::hprintln; +use panic_halt as _; +use rtic::cyccnt::{Instant, U32Ext as _}; +use stm32f4xx_hal::{ + // gpio::gpioa::PA0, + prelude::*, + // rcc::CFGR, + spi::Spi, + stm32, +}; + +//use crate::hal::gpio::{gpioa::PA0, Edge, Input, PullDown}; +//use hal::spi::{Mode, Phase, Polarity}; + +#[rtic::app(device = stm32f4xx_hal::stm32, monotonic = rtic::cyccnt::CYCCNT, peripherals = true)] +const APP: () = { + struct Resources { + // late resources + GPIOA: stm32::GPIOA, + ITM: stm32::ITM, + } + #[init(schedule = [toggle])] + fn init(cx: init::Context) -> init::LateResources { + let mut core = cx.core; + let device = cx.device; + + // Configure led: + // power on GPIOA, RM0368 6.3.11 + device.RCC.ahb1enr.modify(|_, w| w.gpioaen().set_bit()); + // configure PA5 as output, RM0368 8.4.1 + device.GPIOA.moder.modify(|_, w| w.moder5().bits(1)); + + // setup clocks + let rcc = device.RCC.constrain(); + + let clocks = rcc.cfgr.freeze(); + + let stim = &mut core.ITM.stim[0]; + + iprintln!(stim, "pmw3389"); + + // iprintln!(stim, "hclk {:?}", clocks.hclk().into::()); + + // Initialize (enable) the monotonic timer (CYCCNT) + core.DCB.enable_trace(); + // required on Cortex-M7 devices that software lock the DWT (e.g. STM32F7) + DWT::unlock(); + core.DWT.enable_cycle_counter(); + + // Configure SPI + // spi2 + // sck - pb10, (yellow) + // miso - pc2, (red) + // mosi - pc3, (orange) + // ncs - pb4, (long yellow) + // motion - (brown) + // + // +5, (white) + // gnd, (black) + + let gpiob = device.GPIOB.split(); + let gpioc = device.GPIOC.split(); + + let sck = gpiob.pb10.into_alternate_af5(); + let miso = gpioc.pc2.into_alternate_af5(); + let mosi = gpioc.pc3.into_alternate_af5(); + let cs = gpiob.pb4.into_push_pull_output(); + + let spi = Spi::spi2( + device.SPI2, + (sck, miso, mosi), + // Mode { + // polarity: Polarity::IdleLow, + // phase: Phase::CaptureOnFirstTransition, + // }, + MODE_0, + stm32f4xx_hal::time::KiloHertz(2000).into(), + clocks, + ); + + iprintln!(stim, "clocks:\n hclk {}", clocks.hclk().0); + + let mut pmw3389 = pmw3389::Pmw3389::new(spi, cs).unwrap(); + let id = pmw3389.product_id().unwrap(); + iprintln!(stim, "id {}", id); + + // semantically, the monotonic timer is frozen at time "zero" during `init` + // NOTE do *not* call `Instant::now` in this context; it will return a nonsense value + // let now = cx.start; // the start time of the system + + // Schedule `toggle` to run 8e6 cycles (clock cycles) in the future + // cx.schedule.toggle(now + 8_000_000.cycles()).unwrap(); + + // pass on late resources + init::LateResources { + GPIOA: device.GPIOA, + ITM: core.ITM, + } + } + + #[task(resources = [GPIOA, ITM], schedule = [toggle])] + fn toggle(cx: toggle::Context) { + static mut TOGGLE: bool = false; + iprintln!(&mut cx.resources.ITM.stim[0], "foo @ {:?}", Instant::now()); + + if *TOGGLE { + cx.resources.GPIOA.bsrr.write(|w| w.bs5().set_bit()); + } else { + cx.resources.GPIOA.bsrr.write(|w| w.br5().set_bit()); + } + + *TOGGLE = !*TOGGLE; + cx.schedule + .toggle(cx.scheduled + 8_000_000.cycles()) + .unwrap(); + } + + extern "C" { + fn EXTI0(); + } +}; + +mod pmw3389 { + + use embedded_hal::blocking::spi::{Transfer, Write}; + use embedded_hal::digital::v2::OutputPin; + use embedded_hal::spi::Mode; + + #[allow(dead_code)] + #[derive(Clone, Copy)] + pub enum Register { + ProductId = 0x00, + RevisionId = 0x01, + Motion = 0x02, + DeltaXL = 0x03, + DeltaXH = 0x04, + DeltaYL = 0x05, + DeltaYH = 0x06, + SQUAL = 0x07, + RawDataSum = 0x08, + MaximumRawdata = 0x09, + MinimumRawdata = 0x0A, + ShutterLower = 0x0B, + ShutterUpper = 0x0C, + RippleControl = 0x0D, + ResoultionL = 0x0E, + ResolutionH = 0x0F, + Config2 = 0x10, + AngleTune = 0x11, + FrameCapture = 0x12, + SROMEnable = 0x13, + RunDownshift = 0x14, + Rest1RateLower = 0x15, + Rest1RateUpper = 0x16, + Rest1Downshift = 0x17, + Rest2RateLower = 0x18, + Rest2RateUpper = 0x19, + Rest2Downshift = 0x1A, + Rest3RateLower = 0x1B, + Rest3RateUpper = 0x1C, + Observation = 0x24, + DataOutLower = 0x25, + DataOutUpper = 0x26, + RawDataDump = 0x29, + SROMId = 0x2A, + MinSQRun = 0x2B, + RawDataThreshold = 0x2C, + Control2 = 0x2D, + Config5L = 0x2E, + Config5H = 0x2F, + PowerUpReset = 0x3A, + Shutdown = 0x3B, + InverseProductID = 0x3F, + LiftCutoffTune3 = 0x41, + AngleSnap = 0x42, + LiftCutoffTune1 = 0x4A, + MotionBurst = 0x50, + LiftCutoffTune1Timeout = 0x58, + LiftCutoffTune1MinLength = 0x5A, + SROMLoadBurst = 0x62, + LiftConfig = 0x63, + RawDataBurst = 0x64, + LiftCutoffTune2 = 0x65, + LiftCutoffTune2Timeout = 0x71, + LiftCutoffTune2MinLength = 0x72, + PWMPeriodCnt = 0x73, + PWMWidthCnt = 0x74, + } + + impl Register { + fn addr(self) -> u8 { + self as u8 + } + } + + const READ: u8 = 1 << 7; + const WRITE: u8 = 0 << 7; + const MULTI: u8 = 1 << 6; + const SINGLE: u8 = 0 << 6; + + pub struct Pmw3389<SPI, CS> { + spi: SPI, + cs: CS, + } + + impl<SPI, CS, E> Pmw3389<SPI, CS> + where + SPI: Transfer<u8, Error = E> + Write<u8, Error = E>, + CS: OutputPin, + { + /// Creates a new driver from a SPI peripheral and a NCS pin + pub fn new(spi: SPI, cs: CS) -> Result<Self, E> { + let mut pmw3389 = Pmw3389 { spi, cs }; + + // power up and enable all the axes + // l3gd20.write_register(Register::CTRL_REG1, 0b00_00_1_111)?; + + Ok(pmw3389) + } + + /// Reads the ProductId register; should return `0x47` + pub fn product_id(&mut self) -> Result<u8, E> { + self.read_register(Register::ProductId) + } + + // /// Temperature measurement + gyroscope measurements + // pub fn all(&mut self) -> Result<Measurements, E> { + // let mut bytes = [0u8; 9]; + // self.read_many(Register::OUT_TEMP, &mut bytes)?; + + // Ok(Measurements { + // gyro: I16x3 { + // x: (bytes[3] as u16 + ((bytes[4] as u16) << 8)) as i16, + // y: (bytes[5] as u16 + ((bytes[6] as u16) << 8)) as i16, + // z: (bytes[7] as u16 + ((bytes[8] as u16) << 8)) as i16, + // }, + // temp: bytes[1] as i8, + // }) + // } + + // /// Gyroscope measurements + // pub fn gyro(&mut self) -> Result<I16x3, E> { + // let mut bytes = [0u8; 7]; + // self.read_many(Register::OUT_X_L, &mut bytes)?; + + // Ok(I16x3 { + // x: (bytes[1] as u16 + ((bytes[2] as u16) << 8)) as i16, + // y: (bytes[3] as u16 + ((bytes[4] as u16) << 8)) as i16, + // z: (bytes[5] as u16 + ((bytes[6] as u16) << 8)) as i16, + // }) + // } + + // /// Temperature sensor measurement + // pub fn temp(&mut self) -> Result<i8, E> { + // Ok(self.read_register(Register::OUT_TEMP)? as i8) + // } + + // /// Read `STATUS_REG` of sensor + // pub fn status(&mut self) -> Result<Status, E> { + // let sts = self.read_register(Register::STATUS_REG)?; + // Ok(Status::from_u8(sts)) + // } + + // /// Get the current Output Data Rate + // pub fn odr(&mut self) -> Result<Odr, E> { + // // Read control register + // let reg1 = self.read_register(Register::CTRL_REG1)?; + // Ok(Odr::from_u8(reg1)) + // } + + // /// Set the Output Data Rate + // pub fn set_odr(&mut self, odr: Odr) -> Result<&mut Self, E> { + // self.change_config(Register::CTRL_REG1, odr) + // } + + // /// Get current Bandwidth + // pub fn bandwidth(&mut self) -> Result<Bandwidth, E> { + // let reg1 = self.read_register(Register::CTRL_REG1)?; + // Ok(Bandwidth::from_u8(reg1)) + // } + + // /// Set low-pass cut-off frequency (i.e. bandwidth) + // /// + // /// See `Bandwidth` for further explanation + // pub fn set_bandwidth(&mut self, bw: Bandwidth) -> Result<&mut Self, E> { + // self.change_config(Register::CTRL_REG1, bw) + // } + + // /// Get the current Full Scale Selection + // /// + // /// This is the sensitivity of the sensor, see `Scale` for more information + // pub fn scale(&mut self) -> Result<Scale, E> { + // let scl = self.read_register(Register::CTRL_REG4)?; + // Ok(Scale::from_u8(scl)) + // } + + // /// Set the Full Scale Selection + // /// + // /// This sets the sensitivity of the sensor, see `Scale` for more + // /// information + // pub fn set_scale(&mut self, scale: Scale) -> Result<&mut Self, E> { + // self.change_config(Register::CTRL_REG4, scale) + // } + + fn read_register(&mut self, reg: Register) -> Result<u8, E> { + let _ = self.cs.set_low(); + + let mut buffer = [reg.addr() | SINGLE | READ, 0]; + self.spi.transfer(&mut buffer)?; + + let _ = self.cs.set_high(); + + Ok(buffer[1]) + } + + /// Read multiple bytes starting from the `start_reg` register. + /// This function will attempt to fill the provided buffer. + fn read_many(&mut self, start_reg: Register, buffer: &mut [u8]) -> Result<(), E> { + let _ = self.cs.set_low(); + buffer[0] = start_reg.addr() | MULTI | READ; + self.spi.transfer(buffer)?; + let _ = self.cs.set_high(); + + Ok(()) + } + + fn write_register(&mut self, reg: Register, byte: u8) -> Result<(), E> { + let _ = self.cs.set_low(); + + let buffer = [reg.addr() | SINGLE | WRITE, byte]; + self.spi.write(&buffer)?; + + let _ = self.cs.set_high(); + + Ok(()) + } + + // /// Change configuration in register + // /// + // /// Helper function to update a particular part of a register without + // /// affecting other parts of the register that might contain desired + // /// configuration. This allows the `L3gd20` struct to be used like + // /// a builder interface when configuring specific parameters. + // fn change_config<B: BitValue>(&mut self, reg: Register, bits: B) -> Result<&mut Self, E> { + // // Create bit mask from width and shift of value + // let mask = B::mask() << B::shift(); + // // Extract the value as u8 + // let bits = (bits.value() << B::shift()) & mask; + // // Read current value of register + // let current = self.read_register(reg)?; + // // Use supplied mask so we don't affect more than necessary + // let masked = current & !mask; + // // Use `or` to apply the new value without affecting other parts + // let new_reg = masked | bits; + // self.write_register(reg, new_reg)?; + // Ok(self) + // } + } +} diff --git a/examples/rtic_blinky.rs b/examples/rtic_blinky.rs new file mode 100644 index 0000000..9bf9bb2 --- /dev/null +++ b/examples/rtic_blinky.rs @@ -0,0 +1,67 @@ +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use cortex_m::peripheral::DWT; +use cortex_m_semihosting::hprintln; +use panic_halt as _; +use rtic::cyccnt::{Instant, U32Ext as _}; +use stm32f4xx_hal::stm32; + +#[rtic::app(device = stm32f4xx_hal::stm32, monotonic = rtic::cyccnt::CYCCNT, peripherals = true)] +const APP: () = { + struct Resources { + // late resources + GPIOA: stm32::GPIOA, + } + #[init(schedule = [toggle])] + fn init(cx: init::Context) -> init::LateResources { + let mut core = cx.core; + let device = cx.device; + + // Initialize (enable) the monotonic timer (CYCCNT) + core.DCB.enable_trace(); + // required on Cortex-M7 devices that software lock the DWT (e.g. STM32F7) + DWT::unlock(); + core.DWT.enable_cycle_counter(); + + // semantically, the monotonic timer is frozen at time "zero" during `init` + // NOTE do *not* call `Instant::now` in this context; it will return a nonsense value + let now = cx.start; // the start time of the system + + // Schedule `toggle` to run 8e6 cycles (clock cycles) in the future + cx.schedule.toggle(now + 8_000_000.cycles()).unwrap(); + + // power on GPIOA, RM0368 6.3.11 + device.RCC.ahb1enr.modify(|_, w| w.gpioaen().set_bit()); + // configure PA5 as output, RM0368 8.4.1 + device.GPIOA.moder.modify(|_, w| w.moder5().bits(1)); + + // pass on late resources + init::LateResources { + GPIOA: device.GPIOA, + } + } + + #[task(resources = [GPIOA], schedule = [toggle])] + fn toggle(cx: toggle::Context) { + static mut TOGGLE: bool = false; + hprintln!("foo @ {:?}", Instant::now()).unwrap(); + + if *TOGGLE { + cx.resources.GPIOA.bsrr.write(|w| w.bs5().set_bit()); + } else { + cx.resources.GPIOA.bsrr.write(|w| w.br5().set_bit()); + } + + *TOGGLE = !*TOGGLE; + cx.schedule + .toggle(cx.scheduled + 8_000_000.cycles()) + .unwrap(); + } + + extern "C" { + fn EXTI0(); + } +}; -- GitLab