diff --git a/boards/nordic/nrf52840dk/src/main.rs b/boards/nordic/nrf52840dk/src/main.rs index 57c42436c80..8cfb2664736 100644 --- a/boards/nordic/nrf52840dk/src/main.rs +++ b/boards/nordic/nrf52840dk/src/main.rs @@ -68,7 +68,8 @@ use kernel::component::Component; #[allow(unused_imports)] use kernel::{debug, debug_gpio, debug_verbose, static_init}; use nrf52840::gpio::Pin; -use nrf52dk_base::{SpiMX25R6435FPins, SpiPins, UartPins}; + +use nrf52dk_base::{QdecPins, SpiMX25R6435FPins, SpiPins, UartPins}; // The nRF52840DK LEDs (see back of board) const LED1_PIN: Pin = Pin::P0_13; @@ -96,6 +97,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; + /// UART Writer pub mod io; @@ -212,6 +216,7 @@ pub unsafe fn reset_handler() { FAULT_RESPONSE, nrf52840::uicr::Regulator0Output::DEFAULT, false, + &QdecPins::new(QDEC_PIN_A, QDEC_PIN_B), chip, ); } diff --git a/boards/nordic/nrf52dk_base/src/lib.rs b/boards/nordic/nrf52dk_base/src/lib.rs index a746bcdf717..88bf7e39640 100644 --- a/boards/nordic/nrf52dk_base/src/lib.rs +++ b/boards/nordic/nrf52dk_base/src/lib.rs @@ -1,7 +1,6 @@ //! Shared setup for nrf52dk boards. #![no_std] - #[allow(unused_imports)] use kernel::{create_capability, debug, debug_gpio, debug_verbose, static_init}; @@ -12,10 +11,10 @@ use kernel::capabilities; use kernel::common::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::component::Component; use kernel::hil; +use kernel::hil::gpio::{Configure, FloatingState}; use nrf52::gpio::Pin; use nrf52::rtc::Rtc; use nrf52::uicr::Regulator0Output; - pub mod nrf52_components; use nrf52_components::ble::BLEComponent; use nrf52_components::ieee802154::Ieee802154Component; @@ -71,6 +70,19 @@ impl UartPins { } } +/// Pins for the QDEC +#[derive(Debug)] +pub struct QdecPins { + pin_a: Pin, + pin_b: Pin, +} + +impl QdecPins { + pub fn new(pin_a: Pin, pin_b: Pin) -> Self { + Self { pin_a, pin_b } + } +} + /// Supported drivers by the platform pub struct Platform { ble_radio: &'static capsules::ble_advertising_driver::BLE< @@ -96,6 +108,7 @@ pub struct Platform { 'static, capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>, >, + qdec: &'static capsules::qdec::QdecInterface<'static>, // The nRF52dk does not have the flash chip on it, so we make this optional. nonvolatile_storage: Option<&'static capsules::nonvolatile_storage_driver::NonvolatileStorage<'static>>, @@ -113,6 +126,7 @@ impl kernel::Platform for Platform { capsules::led::DRIVER_NUM => f(Some(self.led)), capsules::button::DRIVER_NUM => f(Some(self.button)), capsules::rng::DRIVER_NUM => f(Some(self.rng)), + capsules::qdec::DRIVER_NUM => f(Some(self.qdec)), capsules::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), capsules::ieee802154::DRIVER_NUM => match self.ieee802154_radio { Some(radio) => f(Some(radio)), @@ -150,6 +164,7 @@ pub unsafe fn setup_board( app_fault_response: kernel::procs::FaultResponse, reg_vout: Regulator0Output, nfc_as_gpios: bool, + qdec_pins: &QdecPins, chip: &'static nrf52::chip::NRF52, ) { // Make non-volatile memory writable and activate the reset button @@ -412,6 +427,26 @@ pub unsafe fn setup_board( ); DynamicDeferredCall::set_global_instance(dynamic_deferred_call); + //START: QDEC INITIALIZATION + gpio_port[qdec_pins.pin_a].make_input(); + gpio_port[qdec_pins.pin_b].make_input(); + gpio_port[qdec_pins.pin_a].set_floating_state(FloatingState::PullUp); + gpio_port[qdec_pins.pin_b].set_floating_state(FloatingState::PullUp); + + let qdec_nrf52 = &mut nrf52::qdec::QDEC; + qdec_nrf52.set_pins( + nrf52::pinmux::Pinmux::new(qdec_pins.pin_a as u32), + nrf52::pinmux::Pinmux::new(qdec_pins.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(&nrf52::qdec::QDEC, qdec); + let platform = Platform { button: button, ble_radio: ble_radio, @@ -425,6 +460,7 @@ pub unsafe fn setup_board( alarm: alarm, analog_comparator: analog_comparator, nonvolatile_storage: nonvolatile_storage, + qdec: qdec, ipc: kernel::ipc::IPC::new(board_kernel, &memory_allocation_capability), }; @@ -445,6 +481,5 @@ pub unsafe fn setup_board( app_fault_response, &process_management_capability, ); - board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); } diff --git a/capsules/README.md b/capsules/README.md index 8bace344a12..6ab2e69c6b0 100644 --- a/capsules/README.md +++ b/capsules/README.md @@ -81,7 +81,7 @@ These capsules provide a `Driver` interface for common MCU peripherals. - **[I2C_MASTER_SLAVE](src/i2c_master_slave_driver.rs)**: I2C master and slave access. - **[RNG](src/rng.rs)**: Random number generation. - **[SPI](src/spi.rs)**: SPI master and 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 9305e6a7bae..81f383273ca 100644 --- a/capsules/src/driver.rs +++ b/capsules/src/driver.rs @@ -60,5 +60,6 @@ pub enum NUM { // Misc Buzzer = 0x90000, + Qdec = 0x90001, } } diff --git a/capsules/src/lib.rs b/capsules/src/lib.rs index 30683888ca7..7b010c36d83 100644 --- a/capsules/src/lib.rs +++ b/capsules/src/lib.rs @@ -44,6 +44,7 @@ pub mod nonvolatile_to_pages; pub mod nrf51822_serialization; 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 00000000000..4a93701d302 --- /dev/null +++ b/capsules/src/qdec.rs @@ -0,0 +1,108 @@ +//! Provides userspace access to the Qdec on a board. +//! +//! 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, + pos: u32, +} + +impl QdecInterface<'a> { + /// Create a new instance of the QdecInterface + pub fn new(driver: &'a dyn hil::qdec::QdecDriver, grant: Grant) -> QdecInterface<'a> { + QdecInterface { + 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 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.pos = app.pos + self.driver.get_acc(); + app.callback + .map(|mut cb| cb.schedule(app.pos 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 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.enabled(), + 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 5bc9d5fd12d..430740d4fb1 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 5b6377050da..53a4d6cdba3 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 00000000000..5ee59f9c5a5 --- /dev/null +++ b/chips/nrf52/src/qdec.rs @@ -0,0 +1,278 @@ +//! 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), + (0x550 => @END), + } +} + +register_bitfields![u32, + Task [ + 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 0 + ], + PinSelect [ + Pin OFFSET(0) NUMBITS(5), + Port OFFSET(5) NUMBITS(1), + 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 [ + LedPol OFFSET(0) NUMBITS(1) [ + ActiveLow = 0, + ActiveHigh = 1 + ] + ], + Sample [ + SAMPLE 1 + ], + SampPer [ + SAMPLEPER OFFSET(0) NUMBITS(4) [ + us128 = 0, + us256 = 1, + us512 = 2, + us1024 = 3, + us2048 = 4, + us4096 = 5, + us8192 = 6, + us16384 = 7, + ms32 = 8, + ms65 = 9, + ms131 = 10 + ] + ], + ReportPer [ + REPORTPER OFFSET(0) NUMBITS(4) [ + hz10 = 0, + hz40 = 1, + hz80 = 2, + hz120 = 3, + hz160 = 4, + hz200 = 5, + hz240 = 6, + hz280 = 7, + hz1 = 8 + ] + ], + Acc [ + ACC OFFSET(0) NUMBITS(32) + ], + AccDbl [ + 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(); + +#[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 => { /*No handling for REPORTRDY*/ } + 2 => { + client.overflow(); + } + 3 => { /*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 is_enabled(&self) -> ReturnCode { + let regs = &*self.registers; + let result = if regs.enable.is_set(Task::ENABLE) { + ReturnCode::SUCCESS + } else { + ReturnCode::FAIL + }; + 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 enabled(&self) -> ReturnCode { + self.is_enabled() + } + + fn get_acc(&self) -> u32 { + let regs = &*self.registers; + regs.tasks_readclracc.write(Task::ENABLE::SET); + let val = regs.acc_read.read(Acc::ACC); + val + } + + fn set_client(&self, client: &'static dyn kernel::hil::qdec::QdecClient) { + self.client.set(client); + } +} diff --git a/kernel/src/hil/mod.rs b/kernel/src/hil/mod.rs index e30392b8125..6bf395a12e4 100644 --- a/kernel/src/hil/mod.rs +++ b/kernel/src/hil/mod.rs @@ -14,6 +14,7 @@ pub mod i2c; pub mod led; pub mod nonvolatile_storage; pub mod pwm; +pub mod qdec; pub mod radio; pub mod rng; pub mod sensors; diff --git a/kernel/src/hil/qdec.rs b/kernel/src/hil/qdec.rs new file mode 100644 index 00000000000..22a58a2c283 --- /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 SAMPLERDY interrupt + fn enable_interrupts(&self) -> ReturnCode; + + /// Enables the Qdec, returning error if Qdec does not exist + fn enable_qdec(&self) -> ReturnCode; + + /// Checks if the qdec has been enabled + fn enabled(&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) -> u32; +} + +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); +}