-
Notifications
You must be signed in to change notification settings - Fork 109
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
471: Synchornous ADC API r=jrvanwhy a=AnneOnciulescu ### Pull Request Overview This PR adds a Synchornous ADC API, which includes: - function for checking the existence of the driver - functions for initating reading of a sample, registering and unregistering a listener - function for synchronous sample readings - unit tests Alongside the API, this PR adds a fake driver (and unit tests for it) for testing the API. ### Testing Strategy This pull request was tested using unit tests made specifically for this API and fake driver. ### TODO or Help Wanted This pull request still needs feedback / code review. Tests needed for get_resolution_bits() and get_reference_voltage_mv(). Another PR will follow up with the implementation and tests for continuous sampling. Co-authored-by: Anne Onciulescu <anne.onciulescu@gmail.com>
- Loading branch information
Showing
9 changed files
with
369 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[package] | ||
name = "libtock_adc" | ||
version = "0.1.0" | ||
authors = ["Tock Project Developers <tock-dev@googlegroups.com>"] | ||
license = "MIT/Apache-2.0" | ||
edition = "2021" | ||
repository = "https://www.github.com/tock/libtock-rs" | ||
description = "libtock adc driver" | ||
|
||
[dependencies] | ||
libtock_platform = { path = "../../platform" } | ||
|
||
[dev-dependencies] | ||
libtock_unittest = { path = "../../unittest" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
#![no_std] | ||
|
||
use core::cell::Cell; | ||
use libtock_platform::{ | ||
share, subscribe::OneId, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall, | ||
}; | ||
|
||
pub struct Adc<S: Syscalls>(S); | ||
|
||
impl<S: Syscalls> Adc<S> { | ||
/// Returns Ok() if the driver was present.This does not necessarily mean | ||
/// that the driver is working. | ||
pub fn exists() -> Result<(), ErrorCode> { | ||
S::command(DRIVER_NUM, EXISTS, 0, 0).to_result() | ||
} | ||
|
||
// Initiate a sample reading | ||
pub fn read_single_sample() -> Result<(), ErrorCode> { | ||
S::command(DRIVER_NUM, SINGLE_SAMPLE, 0, 0).to_result() | ||
} | ||
|
||
// Register a listener to be called when the ADC conversion is finished | ||
pub fn register_listener<'share, F: Fn(u16)>( | ||
listener: &'share ADCListener<F>, | ||
subscribe: share::Handle<Subscribe<'share, S, DRIVER_NUM, 0>>, | ||
) -> Result<(), ErrorCode> { | ||
S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, listener) | ||
} | ||
|
||
/// Unregister the events listener | ||
pub fn unregister_listener() { | ||
S::unsubscribe(DRIVER_NUM, 0) | ||
} | ||
|
||
/// Initiates a synchronous ADC conversion | ||
/// Returns the converted ADC value or an error | ||
pub fn read_single_sample_sync() -> Result<u16, ErrorCode> { | ||
let sample: Cell<Option<u16>> = Cell::new(None); | ||
let listener = ADCListener(|adc_val| { | ||
sample.set(Some(adc_val)); | ||
}); | ||
share::scope(|subscribe| { | ||
Self::register_listener(&listener, subscribe)?; | ||
Self::read_single_sample()?; | ||
while sample.get() == None { | ||
S::yield_wait(); | ||
} | ||
|
||
match sample.get() { | ||
None => Err(ErrorCode::Busy), | ||
Some(adc_val) => Ok(adc_val), | ||
} | ||
}) | ||
} | ||
|
||
/// Returns the number of ADC resolution bits | ||
pub fn get_resolution_bits() -> Result<u32, ErrorCode> { | ||
S::command(DRIVER_NUM, GET_RES_BITS, 0, 0).to_result() | ||
} | ||
|
||
/// Returns the reference voltage in millivolts (mV) | ||
pub fn get_reference_voltage_mv() -> Result<u32, ErrorCode> { | ||
S::command(DRIVER_NUM, GET_VOLTAGE_REF, 0, 0).to_result() | ||
} | ||
} | ||
|
||
pub struct ADCListener<F: Fn(u16)>(pub F); | ||
|
||
impl<F: Fn(u16)> Upcall<OneId<DRIVER_NUM, 0>> for ADCListener<F> { | ||
fn upcall(&self, adc_val: u32, _arg1: u32, _arg2: u32) { | ||
self.0(adc_val as u16) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests; | ||
|
||
// ----------------------------------------------------------------------------- | ||
// Driver number and command IDs | ||
// ----------------------------------------------------------------------------- | ||
|
||
const DRIVER_NUM: u32 = 0x5; | ||
|
||
// Command IDs | ||
|
||
const EXISTS: u32 = 0; | ||
const SINGLE_SAMPLE: u32 = 1; | ||
// const REPEAT_SINGLE_SAMPLE: u32 = 2; | ||
// const MULTIPLE_SAMPLE: u32 = 3; | ||
// const CONTINUOUS_BUFF_SAMPLE: u32 = 4; | ||
// const STOP_SAMPLE: u32 = 5; | ||
const GET_RES_BITS: u32 = 101; | ||
const GET_VOLTAGE_REF: u32 = 102; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
use core::cell::Cell; | ||
use libtock_platform::{share, ErrorCode, Syscalls, YieldNoWaitReturn}; | ||
use libtock_unittest::fake; | ||
|
||
type Adc = super::Adc<fake::Syscalls>; | ||
|
||
#[test] | ||
fn no_driver() { | ||
let _kernel = fake::Kernel::new(); | ||
assert_eq!(Adc::exists(), Err(ErrorCode::NoDevice)); | ||
} | ||
|
||
#[test] | ||
fn driver_check() { | ||
let kernel = fake::Kernel::new(); | ||
let driver = fake::Adc::new(); | ||
kernel.add_driver(&driver); | ||
|
||
assert_eq!(Adc::exists(), Ok(())); | ||
} | ||
|
||
#[test] | ||
fn read_single_sample() { | ||
let kernel = fake::Kernel::new(); | ||
let driver = fake::Adc::new(); | ||
kernel.add_driver(&driver); | ||
|
||
assert_eq!(Adc::read_single_sample(), Ok(())); | ||
assert!(driver.is_busy()); | ||
|
||
assert_eq!(Adc::read_single_sample(), Err(ErrorCode::Busy)); | ||
assert_eq!(Adc::read_single_sample_sync(), Err(ErrorCode::Busy)); | ||
} | ||
|
||
#[test] | ||
fn register_unregister_listener() { | ||
let kernel = fake::Kernel::new(); | ||
let driver = fake::Adc::new(); | ||
kernel.add_driver(&driver); | ||
|
||
let sample: Cell<Option<u16>> = Cell::new(None); | ||
let listener = crate::ADCListener(|adc_val| { | ||
sample.set(Some(adc_val)); | ||
}); | ||
share::scope(|subscribe| { | ||
assert_eq!(Adc::read_single_sample(), Ok(())); | ||
driver.set_value(100); | ||
assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); | ||
|
||
assert_eq!(Adc::register_listener(&listener, subscribe), Ok(())); | ||
assert_eq!(Adc::read_single_sample(), Ok(())); | ||
driver.set_value(100); | ||
assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); | ||
assert_eq!(sample.get(), Some(100)); | ||
|
||
Adc::unregister_listener(); | ||
assert_eq!(Adc::read_single_sample(), Ok(())); | ||
driver.set_value(100); | ||
assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); | ||
}); | ||
} | ||
|
||
#[test] | ||
fn read_single_sample_sync() { | ||
let kernel = fake::Kernel::new(); | ||
let driver = fake::Adc::new(); | ||
kernel.add_driver(&driver); | ||
|
||
driver.set_value_sync(1000); | ||
assert_eq!(Adc::read_single_sample_sync(), Ok(1000)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
//! A simple libtock-rs example. Checks for adc driver | ||
//! and samples the sensor every 2 seconds. | ||
|
||
#![no_main] | ||
#![no_std] | ||
|
||
use core::fmt::Write; | ||
use libtock::console::Console; | ||
|
||
use libtock::adc::Adc; | ||
use libtock::alarm::{Alarm, Milliseconds}; | ||
use libtock::runtime::{set_main, stack_size}; | ||
|
||
set_main! {main} | ||
stack_size! {0x200} | ||
|
||
fn main() { | ||
if Adc::exists().is_err() { | ||
writeln!(Console::writer(), "adc driver unavailable").unwrap(); | ||
return; | ||
} | ||
|
||
loop { | ||
match Adc::read_single_sample_sync() { | ||
Ok(adc_val) => writeln!(Console::writer(), "Sample: {}\n", adc_val).unwrap(), | ||
Err(_) => writeln!(Console::writer(), "error while reading sample",).unwrap(), | ||
} | ||
|
||
Alarm::sleep_for(Milliseconds(2000)).unwrap(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
//! Fake implementation of the Adc API, documented here: | ||
//! | ||
//! Like the real API, `Adc` controls a fake Adc sensor. It provides | ||
//! a function `set_value` used to immediately call an upcall with a Adc value read by the sensor | ||
//! and a function 'set_value_sync' used to call the upcall when the read command is received. | ||
|
||
use crate::{DriverInfo, DriverShareRef}; | ||
use libtock_platform::{CommandReturn, ErrorCode}; | ||
use std::cell::Cell; | ||
|
||
// The `upcall_on_command` field is set to Some(value) if an upcall(with value as its argument) should be called when read command is received, | ||
// or None otherwise. It was needed for testing `read_sync` library function which simulates a synchronous Adc read, | ||
// because it was impossible to schedule an upcall during the `synchronous` read in other ways. | ||
pub struct Adc { | ||
busy: Cell<bool>, | ||
upcall_on_command: Cell<Option<i32>>, | ||
share_ref: DriverShareRef, | ||
} | ||
|
||
impl Adc { | ||
pub fn new() -> std::rc::Rc<Adc> { | ||
std::rc::Rc::new(Adc { | ||
busy: Cell::new(false), | ||
upcall_on_command: Cell::new(None), | ||
share_ref: Default::default(), | ||
}) | ||
} | ||
|
||
pub fn is_busy(&self) -> bool { | ||
self.busy.get() | ||
} | ||
pub fn set_value(&self, value: i32) { | ||
if self.busy.get() { | ||
self.share_ref | ||
.schedule_upcall(0, (value as u32, 0, 0)) | ||
.expect("Unable to schedule upcall"); | ||
self.busy.set(false); | ||
} | ||
} | ||
pub fn set_value_sync(&self, value: i32) { | ||
self.upcall_on_command.set(Some(value)); | ||
} | ||
} | ||
|
||
impl crate::fake::SyscallDriver for Adc { | ||
fn info(&self) -> DriverInfo { | ||
DriverInfo::new(DRIVER_NUM).upcall_count(1) | ||
} | ||
|
||
fn register(&self, share_ref: DriverShareRef) { | ||
self.share_ref.replace(share_ref); | ||
} | ||
|
||
fn command(&self, command_id: u32, _argument0: u32, _argument1: u32) -> CommandReturn { | ||
match command_id { | ||
EXISTS => crate::command_return::success(), | ||
|
||
SINGLE_SAMPLE => { | ||
if self.busy.get() { | ||
return crate::command_return::failure(ErrorCode::Busy); | ||
} | ||
self.busy.set(true); | ||
if let Some(val) = self.upcall_on_command.take() { | ||
self.set_value(val); | ||
} | ||
crate::command_return::success() | ||
} | ||
_ => crate::command_return::failure(ErrorCode::NoSupport), | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests; | ||
// ----------------------------------------------------------------------------- | ||
// Driver number and command IDs | ||
// ----------------------------------------------------------------------------- | ||
|
||
const DRIVER_NUM: u32 = 0x5; | ||
|
||
// Command IDs | ||
|
||
const EXISTS: u32 = 0; | ||
const SINGLE_SAMPLE: u32 = 1; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
use crate::fake::{self, SyscallDriver}; | ||
use fake::adc::*; | ||
use libtock_platform::{share, DefaultConfig, YieldNoWaitReturn}; | ||
|
||
//Test the command implementation | ||
#[test] | ||
fn command() { | ||
let adc = Adc::new(); | ||
|
||
assert!(adc.command(EXISTS, 1, 2).is_success()); | ||
|
||
assert!(adc.command(SINGLE_SAMPLE, 0, 0).is_success()); | ||
|
||
assert_eq!( | ||
adc.command(SINGLE_SAMPLE, 0, 0).get_failure(), | ||
Some(ErrorCode::Busy) | ||
); | ||
|
||
adc.set_value(100); | ||
assert!(adc.command(SINGLE_SAMPLE, 0, 1).is_success()); | ||
adc.set_value(100); | ||
|
||
adc.set_value_sync(100); | ||
assert!(adc.command(SINGLE_SAMPLE, 0, 1).is_success()); | ||
assert!(adc.command(SINGLE_SAMPLE, 0, 1).is_success()); | ||
} | ||
|
||
// Integration test that verifies Adc works with fake::Kernel and | ||
// libtock_platform::Syscalls. | ||
#[test] | ||
fn kernel_integration() { | ||
use libtock_platform::Syscalls; | ||
let kernel = fake::Kernel::new(); | ||
let adc = Adc::new(); | ||
kernel.add_driver(&adc); | ||
assert!(fake::Syscalls::command(DRIVER_NUM, EXISTS, 1, 2).is_success()); | ||
assert!(fake::Syscalls::command(DRIVER_NUM, SINGLE_SAMPLE, 0, 0).is_success()); | ||
assert_eq!( | ||
fake::Syscalls::command(DRIVER_NUM, SINGLE_SAMPLE, 0, 0).get_failure(), | ||
Some(ErrorCode::Busy) | ||
); | ||
adc.set_value(100); | ||
assert!(fake::Syscalls::command(DRIVER_NUM, SINGLE_SAMPLE, 0, 1).is_success()); | ||
|
||
let listener = Cell::<Option<(u32,)>>::new(None); | ||
share::scope(|subscribe| { | ||
assert_eq!( | ||
fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, &listener), | ||
Ok(()) | ||
); | ||
|
||
adc.set_value(100); | ||
assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); | ||
assert_eq!(listener.get(), Some((100,))); | ||
|
||
adc.set_value(200); | ||
assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); | ||
|
||
assert!(fake::Syscalls::command(DRIVER_NUM, SINGLE_SAMPLE, 0, 1).is_success()); | ||
adc.set_value(200); | ||
assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); | ||
|
||
adc.set_value_sync(200); | ||
assert!(fake::Syscalls::command(DRIVER_NUM, SINGLE_SAMPLE, 0, 1).is_success()); | ||
assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); | ||
}); | ||
} |
Oops, something went wrong.