Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Synchornous ADC API #471

Merged
merged 9 commits into from
May 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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_buttons = { path = "apis/buttons" }
libtock_console = { path = "apis/console" }
Expand All @@ -35,6 +36,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