Skip to content

Commit

Permalink
Merge #471
Browse files Browse the repository at this point in the history
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
bors[bot] and AnneOnciulescu authored May 17, 2023
2 parents 51660c6 + 8928e20 commit 617b21a
Show file tree
Hide file tree
Showing 9 changed files with 369 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ repository = "https://www.github.com/tock/libtock-rs"
version = "0.1.0"

[dependencies]
libtock_adc = { path = "apis/adc"}
libtock_alarm = { path = "apis/alarm" }
libtock_ambient_light = { path = "apis/ambient_light" }
libtock_buttons = { path = "apis/buttons" }
Expand Down Expand Up @@ -39,6 +40,7 @@ debug = true
[workspace]
exclude = ["tock"]
members = [
"apis/adc",
"apis/alarm",
"apis/gpio",
"apis/buttons",
Expand Down
14 changes: 14 additions & 0 deletions apis/adc/Cargo.toml
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" }
93 changes: 93 additions & 0 deletions apis/adc/src/lib.rs
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;
71 changes: 71 additions & 0 deletions apis/adc/src/tests.rs
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));
}
31 changes: 31 additions & 0 deletions examples/adc.rs
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();
}
}
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ extern crate libtock_debug_panic;
pub use libtock_platform as platform;
pub use libtock_runtime as runtime;

pub mod adc {
use libtock_adc as adc;
pub type Adc = adc::Adc<super::runtime::TockSyscalls>;
pub use adc::ADCListener;
}
pub mod alarm {
use libtock_alarm as alarm;
pub type Alarm = alarm::Alarm<super::runtime::TockSyscalls>;
Expand Down
84 changes: 84 additions & 0 deletions unittest/src/fake/adc/mod.rs
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;
67 changes: 67 additions & 0 deletions unittest/src/fake/adc/tests.rs
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);
});
}
Loading

0 comments on commit 617b21a

Please sign in to comment.