diff --git a/boards/nordic/nrf52840dk/src/main.rs b/boards/nordic/nrf52840dk/src/main.rs index 169f3d3932..318067f8f1 100644 --- a/boards/nordic/nrf52840dk/src/main.rs +++ b/boards/nordic/nrf52840dk/src/main.rs @@ -70,6 +70,7 @@ use capsules::virtual_alarm::VirtualMuxAlarm; use kernel::common::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::component::Component; +use kernel::hil::gpio::Configure; #[allow(unused_imports)] use kernel::{capabilities, create_capability, debug, debug_gpio, debug_verbose, static_init}; use nrf52840::gpio::Pin; @@ -101,6 +102,9 @@ const SPI_MX25R6435F_CHIP_SELECT: Pin = Pin::P0_17; const SPI_MX25R6435F_WRITE_PROTECT_PIN: Pin = Pin::P0_22; const SPI_MX25R6435F_HOLD_PIN: Pin = Pin::P0_23; +const QDEC_PIN_A: Pin = Pin::P0_02; +const QDEC_PIN_B: Pin = Pin::P0_29; + // Constants related to the configuration of the 15.4 network stack const SRC_MAC: u16 = 0xf00f; const PAN_ID: u16 = 0xABCD; @@ -158,6 +162,7 @@ pub struct Platform { capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, >, nonvolatile_storage: &'static capsules::nonvolatile_storage_driver::NonvolatileStorage<'static>, + qdec: &'static capsules::qdec::QdecInterface<'static>, } impl kernel::Platform for Platform { @@ -177,6 +182,7 @@ impl kernel::Platform for Platform { capsules::temperature::DRIVER_NUM => f(Some(self.temp)), capsules::analog_comparator::DRIVER_NUM => f(Some(self.analog_comparator)), capsules::nonvolatile_storage_driver::DRIVER_NUM => f(Some(self.nonvolatile_storage)), + capsules::qdec::DRIVER_NUM => f(Some(self.qdec)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), _ => f(None), } @@ -411,6 +417,26 @@ pub unsafe fn reset_handler() { nrf52840::acomp::Comparator )); + // qdec initialization + gpio_port[QDEC_PIN_A].make_input(); + gpio_port[QDEC_PIN_B].make_input(); + gpio_port[QDEC_PIN_A].set_floating_state(kernel::hil::gpio::FloatingState::PullUp); + gpio_port[QDEC_PIN_B].set_floating_state(kernel::hil::gpio::FloatingState::PullUp); + + let qdec_nrf52 = &mut nrf52840::qdec::QDEC; + qdec_nrf52.set_pins( + nrf52840::pinmux::Pinmux::new(QDEC_PIN_A as u32), + nrf52840::pinmux::Pinmux::new(QDEC_PIN_B as u32), + ); + let qdec = static_init!( + capsules::qdec::QdecInterface<'static>, + capsules::qdec::QdecInterface::new( + qdec_nrf52, + board_kernel.create_grant(&memory_allocation_capability) + ) + ); + kernel::hil::qdec::QdecDriver::set_client(&nrf52840::qdec::QDEC, qdec); + nrf52_components::NrfClockComponent::new().finalize(()); let platform = Platform { @@ -426,6 +452,7 @@ pub unsafe fn reset_handler() { alarm, analog_comparator, nonvolatile_storage, + qdec, ipc: kernel::ipc::IPC::new(board_kernel, &memory_allocation_capability), }; diff --git a/capsules/README.md b/capsules/README.md index 2dfd4b270a..62d5c2ca81 100644 --- a/capsules/README.md +++ b/capsules/README.md @@ -85,7 +85,7 @@ These capsules provide a `Driver` interface for common MCU peripherals. - **[RNG](src/rng.rs)**: Random number generation. - **[SPI Controller](src/spi_controller.rs)**: SPI controller device (SPI master) - **[SPI Peripheral](src/spi_peripheral.rs)**: SPI peripheral device (SPI slave) - +- **[QDEC](src/qdec.rs)**: Quadrature DECoder for quadrature-encoded sensor signals ### Helpful Userspace Capsules diff --git a/capsules/src/driver.rs b/capsules/src/driver.rs index 256fc0e9da..65e228066e 100644 --- a/capsules/src/driver.rs +++ b/capsules/src/driver.rs @@ -69,6 +69,7 @@ pub enum NUM { // Misc Buzzer = 0x90000, Screen = 0x90001, - Touch = 0x90002 + Touch = 0x90002, + Qdec = 0x90003, } } diff --git a/capsules/src/lib.rs b/capsules/src/lib.rs index e4423fe052..0c9efb2865 100644 --- a/capsules/src/lib.rs +++ b/capsules/src/lib.rs @@ -52,6 +52,7 @@ pub mod nrf51822_serialization; pub mod panic_button; pub mod pca9544a; pub mod process_console; +pub mod qdec; pub mod rf233; pub mod rf233_const; pub mod rng; diff --git a/capsules/src/qdec.rs b/capsules/src/qdec.rs new file mode 100644 index 0000000000..2937d51aab --- /dev/null +++ b/capsules/src/qdec.rs @@ -0,0 +1,110 @@ +//! Provides userspace access to the Quadrature Decoder (QDEC) on a board. +//! A QDEC provides buffered decoding of quadrature-encoded sensor signals. +//! It is generally used to decode mechanical and optical signals. +//! +//! Usage +//! ----- +//! +//! ```` +//! let qdec = static_init!( +//! capsules::qdec::Qdec<'static>, +//! capsules::qdec::QdecInterface::new(&nrf52::qdec::QDEC, +//! kernel::Grant::create()) +//! ); +//! kernel::hil::QdecDriver.set_client(qdec); +//! ```` +//! #Interrupt Spurred Readings versus Regular Readings +//! An application can either enable interrupts to get the +//! accumulation value or manually read it whenever it wants + +use crate::driver; +use kernel::hil; +use kernel::{AppId, Callback, Driver, Grant, ReturnCode}; + +pub const DRIVER_NUM: usize = driver::NUM::Qdec as usize; + +/// This struct contains the resources necessary for the QdecInterface +pub struct QdecInterface<'a> { + driver: &'a dyn hil::qdec::QdecDriver, + apps: Grant, +} + +#[derive(Default)] +/// This struct contains the necessary fields for an app +pub struct App { + callback: Option, + position: i32, +} + +impl<'a> QdecInterface<'a> { + /// Create a new instance of the QdecInterface + pub fn new(driver: &'a dyn hil::qdec::QdecDriver, grant: Grant) -> Self { + Self { + driver: driver, + apps: grant, + } + } + + fn configure_callback(&self, callback: Option, app_id: AppId) -> ReturnCode { + self.apps + .enter(app_id, |app, _| { + app.callback = callback; + ReturnCode::SUCCESS + }) + .unwrap_or_else(|err| err.into()) + } +} + +impl<'a> hil::qdec::QdecClient for QdecInterface<'a> { + /// Goes through all the apps and if the app is + /// subscribed then it sends back the acc value + fn sample_ready(&self) { + for cntr in self.apps.iter() { + cntr.enter(|app, _| { + app.position = app.position + self.driver.get_acc(); + app.callback + .map(|mut cb| cb.schedule(app.position as usize, 0, 0)); + }); + } + } + + /// Goes through all the apps and if the app recently + /// had an overflow, it records the occurance + fn overflow(&self) { + /*for now, we do not handle overflows*/ + } +} + +impl<'a> Driver for QdecInterface<'a> { + /// Subscribes a client to (newly enabled) interrupts + fn subscribe( + &self, + subscribe_num: usize, + callback: Option, + app_id: AppId, + ) -> ReturnCode { + match subscribe_num { + 0 => self.configure_callback(callback, app_id), + _ => ReturnCode::ENOSUPPORT, + } + } + + /// Command switch statement for various essential processes + /// 0 is a sanity check for the switch statement + /// 1 enables the qdec + /// 2 checks that the qdec is enabled + /// 3 enables inerrupts + /// 4 gets the current displacement stored in the QDEC + fn command(&self, command_num: usize, _: usize, _: usize, _app_id: AppId) -> ReturnCode { + match command_num { + 0 => ReturnCode::SUCCESS, + 1 => self.driver.enable_qdec(), + 2 => self.driver.disable_qdec(), + 3 => self.driver.enable_interrupts(), + 4 => ReturnCode::SuccessWithValue { + value: self.driver.get_acc() as usize, + }, + _ => ReturnCode::ENOSUPPORT, + } + } +} diff --git a/chips/nrf52/src/interrupt_service.rs b/chips/nrf52/src/interrupt_service.rs index 30767072a2..df6ccbbbb9 100644 --- a/chips/nrf52/src/interrupt_service.rs +++ b/chips/nrf52/src/interrupt_service.rs @@ -4,6 +4,7 @@ use crate::ble_radio; use crate::i2c; use crate::ieee802154_radio; use crate::power; +use crate::qdec; use crate::spi; use crate::uart; use kernel::debug; @@ -123,6 +124,7 @@ impl InterruptService for Nrf52InterruptService<'_> { } peripheral_interrupts::SPIM2_SPIS2_SPI2 => spi::SPIM2.handle_interrupt(), peripheral_interrupts::ADC => adc::ADC.handle_interrupt(), + peripheral_interrupts::QDEC => qdec::QDEC.handle_interrupt(), _ => return false, } true diff --git a/chips/nrf52/src/lib.rs b/chips/nrf52/src/lib.rs index c9ae441488..f6b30a7ba6 100644 --- a/chips/nrf52/src/lib.rs +++ b/chips/nrf52/src/lib.rs @@ -18,6 +18,7 @@ pub mod nvmc; pub mod power; pub mod ppi; pub mod pwm; +pub mod qdec; pub mod spi; pub mod uart; pub mod uicr; diff --git a/chips/nrf52/src/qdec.rs b/chips/nrf52/src/qdec.rs new file mode 100644 index 0000000000..39a9b57a97 --- /dev/null +++ b/chips/nrf52/src/qdec.rs @@ -0,0 +1,336 @@ +//! Qdec driver, nRF5x-family +//! set_client(), enable, get_ticks, +//! The nRF5x quadrature decoder +//! +#[allow(unused_imports)] +use core; +use kernel::common::cells::OptionalCell; +use kernel::common::registers::{ + register_bitfields, register_structs, ReadOnly, ReadWrite, WriteOnly, +}; +use kernel::common::StaticRef; +use kernel::ReturnCode; +use nrf5x::pinmux; +// In this section I declare a struct called QdecRegisters, which contains all the +// relevant registers as outlined in the Nordic 5x specification of the Qdec. +register_structs! { + pub QdecRegisters { + /// Start Qdec sampling + (0x000 => tasks_start: WriteOnly), + /// Stop Qdec sampling + (0x004 => tasks_stop: WriteOnly), + /// Read and clear ACC and ACCDBL + (0x008 => tasks_readclracc: WriteOnly), + /// Read and clear ACC + (0x00C => tasks_rdclracc: WriteOnly), + /// Read nad clear ACCDBL + (0x010 => tasks_rdclrdbl: WriteOnly), + ///Reserve space so tasks_rdclrdbl has access to its entire address space (?) + (0x0014 => _reserved), + /// All the events which have interrupts! + (0x100 => events_arr: [ReadWrite; 5]), + (0x0114 => _reserved2), + /// Shortcut register + (0x200 => shorts: ReadWrite), + (0x204 => _reserved3), + /// Enable interrupt + (0x304 => intenset: ReadWrite), + /// Disable Interrupt + (0x308 => intenclr: ReadWrite), + (0x30C => _reserved4), + /// Enable the quad decoder + (0x500 => enable: ReadWrite), + /// Set the LED output pin polarity + (0x504 => ledpol: WriteOnly), + /// Sampling-rate register + (0x508 => sample_per: WriteOnly), + /// Sample register (receives all samples) + (0x50C => sample: WriteOnly), + /// Reportper + (0x510 => report_per: ReadOnly), + /// Accumulating motion-sample values register + (0x514 => acc: ReadOnly), + (0x518 => acc_read: ReadOnly), + (0x51C => reserved6), + (0x520 => psel_a: ReadWrite), + (0x524 => psel_b: ReadWrite), + (0x528 => reserved5), + (0x544 => accdbl: ReadOnly), + (0x548 => accdbl_read: ReadOnly), + (0x54C => @END), + } +} + +register_bitfields![u32, + Task [ + /// Enable + ENABLE 0 + ], + Shorts [ + /// Write '1' to Enable shortcut on EVENTS_COMPARE\[0\] event + REPORTRDY_READCLRACC 0, + /// Write '1' to Enable shortcut on EVENTS_COMPARE\[1\] event + SAMPLERDY_STOP 1, + /// Write '1' to Enable shortcut on EVENTS_COMPARE\[2\] event + REPORTRDY_RDCLRACC 2, + /// Write '1' to Enable shortcut on EVENTS_COMPARE\[3\] event + REPORTRDY_STOP 3, + /// Write '1' to Enable shortcut on EVENTS_COMPARE\[4\] event + DBLRDY_RDCLRDBL 4, + /// Write '1' to Enable shortcut on EVENTS_COMPARE\[5\] event + DBLRDY_STOP 5, + /// Write '1' to Enable shortcut on EVENTS_COMPARE\[6\] event + SAMPLERDY_READCLRACC 6 + ], + Event [ + /// Ready to receive interrupts + READY 0 + ], + PinSelect [ + /// Pins the QDEC is attached to + Pin OFFSET(0) NUMBITS(5), + /// Port the QDEC is using + Port OFFSET(5) NUMBITS(1), + /// Connects pins + Connect OFFSET(31) NUMBITS(1) + ], + Inte [ + /// Write '1' to Enable interrupt on EVENTS_COMPARE\[0\] event + SAMPLERDY 0, + /// Write '1' to Enable interrupt on EVENTS_COMPARE\[1\] event + REPORTRDY 1, + /// Write '1' to Enable interrupt on EVENTS_COMPARE\[2\] event + ACCOF 2, + /// Write '1' to Enable interrupt on EVENTS_COMPARE\[3\] event + DBLRDY 3, + /// Write '1' to Enable interrupt on EVENTS_COMPARE\[4\] event + STOPPED 4 + ], + LedPol [ + /// LED pin polarity + LedPol OFFSET(0) NUMBITS(1) [ + /// Active: Low + ActiveLow = 0, + /// Active: High + ActiveHigh = 1 + ] + ], + Sample [ + /// Last motion sample + SAMPLE 1 + ], + SampPer [ + /// Sample period + SAMPLEPER OFFSET(0) NUMBITS(4) [ + /// Sample every 128 microseconds + us128 = 0, + /// Sample every 256 microseconds + us256 = 1, + /// Sample every 512 microseconds + us512 = 2, + /// Sample every 1024 microseconds + us1024 = 3, + /// Sample every 2048 microseconds + us2048 = 4, + /// Sample every 4096 microseconds + us4096 = 5, + /// Sample every 8192 microseconds + us8192 = 6, + /// Sample every 16384 microseconds + us16384 = 7, + /// Sample every 32 milliseconds + ms32 = 8, + /// Sample every 65 milliseconds + ms65 = 9, + /// Sample every 131 milliseconds + ms131 = 10 + ] + ], + ReportPer [ + /// Captures several samples before being sent out by REPORTRDY event + REPORTPER OFFSET(0) NUMBITS(4) [ + /// 10 samples/report + hz10 = 0, + /// 40 samples/report + hz40 = 1, + /// 80 samples/report + hz80 = 2, + /// 120 samples/report + hz120 = 3, + /// 160 samples/report + hz160 = 4, + /// 200 samples/report + hz200 = 5, + /// 240 samples/report + hz240 = 6, + /// 280 samples/report + hz280 = 7, + /// 1 sample/report + hz1 = 8 + ] + ], + Acc [ + /// Valid motion value samples + ACC OFFSET(0) NUMBITS(32) + ], + AccDbl [ + /// Invalid motion value samples + ACCDBL OFFSET(0) NUMBITS(4) + ] +]; + +/// This defines the beginning of memory which is memory-mapped to the Qdec +/// This base is declared under the Registers Table 3 +const QDEC_BASE: StaticRef = + unsafe { StaticRef::new(0x40012000 as *const QdecRegisters) }; + +pub static mut QDEC: Qdec = Qdec::new(); + +/// Enum defining the current QDEC state of started or stopped +#[derive(PartialEq, Eq)] +enum QdecState { + Start, + Stop, +} +/// Qdec type declaration: gives the Qdec instance registers and a client +pub struct Qdec { + registers: StaticRef, + client: OptionalCell<&'static dyn kernel::hil::qdec::QdecClient>, + state: QdecState, +} + +/// Qdec impl: provides the Qdec type with vital functionality including: +impl Qdec { + const fn new() -> Qdec { + let qdec = Qdec { + registers: QDEC_BASE, + client: OptionalCell::empty(), + state: QdecState::Start, + }; + qdec + } + + /// sets pins_a and pins_b to be the output pins for whatever the encoding device is + pub fn set_pins(&self, pin_a: pinmux::Pinmux, pin_b: pinmux::Pinmux) { + let regs = self.registers; + regs.psel_a.write( + PinSelect::Pin.val(pin_a.into()) + PinSelect::Port.val(0) + PinSelect::Connect.val(0), + ); + regs.psel_b.write( + PinSelect::Pin.val(pin_b.into()) + PinSelect::Port.val(0) + PinSelect::Connect.val(0), + ); + } + + pub fn set_client(&self, client: &'static dyn kernel::hil::qdec::QdecClient) { + self.client.set(client); + } + + /// When an interrupt occurs, check to see if any + /// of the interrupt register bits are set. If it + /// is, then put it in the client's bitmask + pub(crate) fn handle_interrupt(&self) { + let regs = &*self.registers; + self.client.map(|client| { + // For each of 4 possible compare events, if it's happened, + // clear it and sort its bit in val to pass in callback + for i in 0..regs.events_arr.len() { + if regs.events_arr[i].is_set(Event::READY) { + regs.events_arr[i].set(0); + match i { + 0 => { + client.sample_ready(); + } + 1 => { /*For the time being, there will be no implementation in respone + to the firing of the REPORTRDY interrupt. It fires too frequently + in order to utilize it for position measuring. However, by that same + token, it cannot merely be marked as unimplemented since it would + crash the program.*/ + } + 2 => { + client.overflow(); + } + 3 => { /*For the time being, there will be no handling for DBLRDY.*/ } + 4 => { + if self.state == QdecState::Stop { + self.registers.sample_per.write(SampPer::SAMPLEPER.val(5)); + self.registers.tasks_start.write(Task::ENABLE::SET); + } + } + _ => panic!("Unsupported interrupt value {}!", i), + } + } + } + }); + } + + fn enable_samplerdy_interrupts(&self) { + let regs = &*self.registers; + + regs.intenset.write(Inte::REPORTRDY::SET); /*SET REPORT READY*/ + regs.intenset.write(Inte::ACCOF::SET); /*SET ACCOF READY*/ + } + + fn enable(&self) { + let regs = &*self.registers; + regs.enable.write(Task::ENABLE::SET); + regs.tasks_start.write(Task::ENABLE::SET); + } + + fn disable(&self) { + let regs = &*self.registers; + regs.enable.write(Task::ENABLE::CLEAR); + regs.tasks_start.write(Task::ENABLE::CLEAR); + } + + fn is_enabled(&self) -> ReturnCode { + let regs = &*self.registers; + let result = if regs.enable.is_set(Task::ENABLE) { + ReturnCode::SUCCESS + } else { + ReturnCode::FAIL + }; + result + } + + fn is_disabled(&self) -> ReturnCode { + let regs = &*self.registers; + let result = if regs.enable.is_set(Task::ENABLE) { + ReturnCode::FAIL + } else { + ReturnCode::SUCCESS + }; + result + } +} + +impl kernel::hil::qdec::QdecDriver for Qdec { + fn enable_interrupts(&self) -> ReturnCode { + self.enable_samplerdy_interrupts(); + ReturnCode::SUCCESS + } + + fn enable_qdec(&self) -> ReturnCode { + if self.is_enabled() != ReturnCode::SUCCESS { + self.enable(); + } + self.is_enabled() + } + + fn disable_qdec(&self) -> ReturnCode { + if self.is_enabled() == ReturnCode::SUCCESS { + self.disable(); + } + self.is_disabled() + } + + fn get_acc(&self) -> i32 { + let regs = &*self.registers; + regs.tasks_readclracc.write(Task::ENABLE::SET); + let val = regs.acc_read.read(Acc::ACC); + val as i32 + } + + fn set_client(&self, client: &'static dyn kernel::hil::qdec::QdecClient) { + self.client.set(client); + } +} diff --git a/chips/nrf52840/src/lib.rs b/chips/nrf52840/src/lib.rs index 942d0288fd..a3d6d527e0 100644 --- a/chips/nrf52840/src/lib.rs +++ b/chips/nrf52840/src/lib.rs @@ -2,7 +2,7 @@ pub use nrf52::{ acomp, adc, aes, ble_radio, clock, constants, crt1, ficr, i2c, ieee802154_radio, init, nvmc, - pinmux, ppi, pwm, rtc, spi, temperature, timer, trng, uart, uicr, usbd, + pinmux, ppi, pwm, qdec, rtc, spi, temperature, timer, trng, uart, uicr, usbd, }; pub mod chip; pub mod gpio; diff --git a/kernel/src/hil/mod.rs b/kernel/src/hil/mod.rs index 4f42afa02e..86ef06c270 100644 --- a/kernel/src/hil/mod.rs +++ b/kernel/src/hil/mod.rs @@ -16,6 +16,7 @@ pub mod led; pub mod log; pub mod nonvolatile_storage; pub mod pwm; +pub mod qdec; pub mod radio; pub mod rng; pub mod screen; diff --git a/kernel/src/hil/qdec.rs b/kernel/src/hil/qdec.rs new file mode 100644 index 0000000000..b129d543f9 --- /dev/null +++ b/kernel/src/hil/qdec.rs @@ -0,0 +1,33 @@ +//! Interface for a Qdec compatible chip +//! +//! This trait provides a stanfard interface for chips with a +//! quadrature decoder. Note this interface is experimental and +//! may need further updates once implemented on additional chips + +use crate::returncode::ReturnCode; + +pub trait QdecDriver { + /// Sets the client which will receive interrupts + fn set_client(&self, client: &'static dyn QdecClient); + + /// Enables the interrupt collecting and storing samples + fn enable_interrupts(&self) -> ReturnCode; + + /// Enables the Qdec, returning error if QDEC is not working + fn enable_qdec(&self) -> ReturnCode; + + /// Disables the QDEC + fn disable_qdec(&self) -> ReturnCode; + + /// Reads the accumulator value and resets it + /// Note accumulator means the measure of how many ticks the + /// QDEC has moved since the last time the function was called + fn get_acc(&self) -> i32; +} + +pub trait QdecClient { + /// Indicate to the client that the status of the accumulator has changed + fn sample_ready(&self); + /// Indicate to the client that an overflow has occurred + fn overflow(&self); +}