Skip to content

Commit 25b3796

Browse files
committed
Userland app for servomotor
1 parent 5b4d692 commit 25b3796

File tree

9 files changed

+309
-0
lines changed

9 files changed

+309
-0
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ libtock_platform = { path = "platform" }
4343
libtock_proximity = { path = "apis/sensors/proximity" }
4444
libtock_rng = { path = "apis/peripherals/rng" }
4545
libtock_runtime = { path = "runtime" }
46+
libtock_servo = { path = "apis/interface/servo" }
4647
libtock_small_panic = { path = "panic_handlers/small_panic" }
4748
libtock_sound_pressure = { path = "apis/sensors/sound_pressure" }
4849
libtock_spi_controller = { path = "apis/peripherals/spi_controller" }
@@ -72,6 +73,7 @@ members = [
7273
"apis/interface/buzzer",
7374
"apis/interface/console",
7475
"apis/interface/leds",
76+
"apis/interface/servo",
7577
"apis/kernel/low_level_debug",
7678
"apis/peripherals/adc",
7779
"apis/peripherals/alarm",

apis/interface/servo/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "libtock_servo"
3+
version = "0.1.0"
4+
authors = ["Tock Project Developers <[email protected]>"]
5+
license = "Apache-2.0 OR MIT"
6+
edition = "2021"
7+
repository = "https://www.github.com/tock/libtock-rs"
8+
rust-version.workspace = true
9+
description = "libtock servomotor driver"
10+
11+
[dependencies]
12+
libtock_platform = { path = "../../../platform" }
13+
14+
[dev-dependencies]
15+
libtock_unittest = { path = "../../../unittest" }

apis/interface/servo/src/lib.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#![no_std]
2+
3+
use libtock_platform::{ErrorCode, Syscalls};
4+
5+
pub struct Servo<S: Syscalls>(S);
6+
7+
impl<S: Syscalls> Servo<S> {
8+
/// Check whether the driver exists.
9+
pub fn exists() -> Result<(), ErrorCode> {
10+
let val = S::command(DRIVER_NUM, EXISTS, 0, 0).is_success();
11+
if val {
12+
Ok(())
13+
} else {
14+
Err(ErrorCode::Fail)
15+
}
16+
}
17+
/// Returns the number of the servomotors available.
18+
pub fn count() -> Result<u32, ErrorCode> {
19+
S::command(DRIVER_NUM, SERVO_COUNT, 0, 0).to_result()
20+
}
21+
22+
/// Changes the angle of the servo.
23+
///
24+
/// # Arguments
25+
/// - `angle` - the variable that receives the angle
26+
/// (in degrees from 0 to 180) from the servo driver.
27+
/// - `index` - the variable that receives the index of the servomotor.
28+
///
29+
/// # Return values:
30+
/// - `Ok(())`: The attempt at changing the angle was successful.
31+
///
32+
/// # Errors:
33+
/// - `FAIL`: Cannot change the angle.
34+
/// - `INVAL`: The value exceeds u16, indicating it's incorrect
35+
/// since servomotors can only have a maximum of 360 degrees.
36+
/// - `NODEVICE`: The index exceeds the number of servomotors provided.
37+
pub fn set_angle(index: u32, angle: u32) -> Result<(), ErrorCode> {
38+
S::command(DRIVER_NUM, SET_ANGLE, index, angle).to_result()
39+
}
40+
41+
/// Returns the angle of the servo.
42+
///
43+
/// # Arguments
44+
/// - `index` - the variable that receives the index of the servomotor.
45+
///
46+
/// # Return values:
47+
/// - `angle`: The value, in angles from 0 to 360, of the servo.
48+
///
49+
/// # Errors:
50+
/// - `NOSUPPORT`: The servo cannot return its angle.
51+
/// - `NODEVICE`: The index exceeds the number of servomotors provided.
52+
pub fn get_angle(index: u32) -> Result<u32, ErrorCode> {
53+
S::command(DRIVER_NUM, GET_ANGLE, index, 0).to_result()
54+
}
55+
}
56+
57+
#[cfg(test)]
58+
mod tests;
59+
60+
// -----------------------------------------------------------------------------
61+
// Driver number and command IDs
62+
// -----------------------------------------------------------------------------
63+
64+
const DRIVER_NUM: u32 = 0x90009;
65+
66+
// Command IDs
67+
const EXISTS: u32 = 0;
68+
const SERVO_COUNT: u32 = 1;
69+
const SET_ANGLE: u32 = 2;
70+
const GET_ANGLE: u32 = 3;

apis/interface/servo/src/tests.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use libtock_platform::ErrorCode;
2+
use libtock_unittest::fake;
3+
4+
type Servo = super::Servo<fake::Syscalls>;
5+
6+
#[test]
7+
fn no_driver() {
8+
let _kernel = fake::Kernel::new();
9+
assert_eq!(Servo::exists(), Err(ErrorCode::Fail))
10+
}
11+
#[test]
12+
fn exists() {
13+
let kernel = fake::Kernel::new();
14+
let driver = fake::Servo::<2>::new();
15+
kernel.add_driver(&driver);
16+
assert_eq!(Servo::exists(), Ok(()));
17+
}
18+
#[test]
19+
fn count() {
20+
let kernel = fake::Kernel::new();
21+
let driver = fake::Servo::<2>::new();
22+
kernel.add_driver(&driver);
23+
assert_eq!(Servo::count(), Ok(2));
24+
}
25+
#[test]
26+
fn set_angle() {
27+
let kernel = fake::Kernel::new();
28+
let driver = fake::Servo::<2>::new();
29+
kernel.add_driver(&driver);
30+
assert_eq!(Servo::set_angle(1, 90), Ok(()));
31+
}
32+
#[test]
33+
fn get_angle() {
34+
let kernel = fake::Kernel::new();
35+
let driver = fake::Servo::<2>::new();
36+
kernel.add_driver(&driver);
37+
assert_eq!(Servo::set_angle(1, 45), Ok(()));
38+
assert_eq!(Servo::get_angle(1), Ok(45));
39+
}

examples/servo.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#![no_main]
2+
#![no_std]
3+
use core::fmt::Write;
4+
use libtock::alarm::{Alarm, Milliseconds};
5+
use libtock::console::Console;
6+
use libtock::runtime::{set_main, stack_size};
7+
use libtock::servo::Servo;
8+
use libtock_platform::ErrorCode;
9+
10+
set_main! {main}
11+
stack_size! {0x300}
12+
13+
fn main() {
14+
//Checks if the driver exists.
15+
if Err(ErrorCode::Fail) == Servo::exists() {
16+
writeln!(Console::writer(), "The driver could not be found").unwrap();
17+
return;
18+
}
19+
let count = Servo::count().unwrap();
20+
21+
writeln!(
22+
Console::writer(),
23+
"The number of available servomotors is {:?}",
24+
count
25+
)
26+
.unwrap();
27+
28+
let index: u32 = 0; // the first index available.
29+
30+
// Changes the angle of the servomotor from 0 to 180 degrees (waiting 0.1 ms between every change).
31+
// "i" represents the angle we set the servomotor at.
32+
for i in 0..180 {
33+
let val1 = Servo::set_angle(index, i); // stores the value returned by set_angle
34+
let val2 = Servo::get_angle(index); // stores the value returned by get_angle
35+
36+
if val1 == Err(ErrorCode::Fail) {
37+
writeln!(
38+
Console::writer(),
39+
"The provided angle exceeds the servo's limit"
40+
)
41+
.unwrap();
42+
} else if val2 == Err(ErrorCode::NoSupport) {
43+
writeln!(Console::writer(), "The servo cannot return its angle").unwrap();
44+
} else if val1 == Err(ErrorCode::NoDevice) {
45+
writeln!(
46+
Console::writer(),
47+
"The index exceeds the number of provided servomotors"
48+
)
49+
.unwrap();
50+
} else if val2 == Err(ErrorCode::NoDevice) {
51+
writeln!(
52+
Console::writer(),
53+
"The index exceeds the number of provided servomotors"
54+
)
55+
.unwrap();
56+
}
57+
Alarm::sleep_for(Milliseconds(100)).unwrap();
58+
}
59+
}

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ pub mod rng {
9292
pub type Rng = rng::Rng<super::runtime::TockSyscalls>;
9393
pub use rng::RngListener;
9494
}
95+
pub mod servo {
96+
use libtock_servo as servo;
97+
pub type Servo = servo::Servo<super::runtime::TockSyscalls>;
98+
}
9599
pub mod sound_pressure {
96100
use libtock_sound_pressure as sound_pressure;
97101
pub type SoundPressure = sound_pressure::SoundPressure<super::runtime::TockSyscalls>;

unittest/src/fake/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ mod leds;
2424
mod low_level_debug;
2525
mod ninedof;
2626
mod proximity;
27+
mod servo;
2728
mod sound_pressure;
2829
mod syscall_driver;
2930
mod syscalls;
@@ -44,6 +45,7 @@ pub use leds::Leds;
4445
pub use low_level_debug::{LowLevelDebug, Message};
4546
pub use ninedof::{NineDof, NineDofData};
4647
pub use proximity::Proximity;
48+
pub use servo::Servo;
4749
pub use sound_pressure::SoundPressure;
4850
pub use syscall_driver::SyscallDriver;
4951
pub use syscalls::Syscalls;

unittest/src/fake/servo/mod.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
use std::cell::Cell;
2+
3+
use crate::DriverInfo;
4+
use libtock_platform::{CommandReturn, ErrorCode};
5+
6+
pub struct Servo<const NUM_SERVO: usize> {
7+
servo: [Cell<u16>; NUM_SERVO],
8+
}
9+
10+
impl<const NUM_SERVO: usize> Servo<NUM_SERVO> {
11+
pub fn new() -> std::rc::Rc<Servo<NUM_SERVO>> {
12+
#[allow(clippy::declare_interior_mutable_const)]
13+
const ANGLE: Cell<u16> = Cell::new(0);
14+
std::rc::Rc::new(Servo {
15+
servo: [ANGLE; NUM_SERVO],
16+
})
17+
}
18+
}
19+
20+
impl<const NUM_SERVO: usize> crate::fake::SyscallDriver for Servo<NUM_SERVO> {
21+
fn info(&self) -> DriverInfo {
22+
DriverInfo::new(DRIVER_NUM)
23+
}
24+
25+
fn command(&self, command_num: u32, servo_index: u32, angle: u32) -> CommandReturn {
26+
match command_num {
27+
EXISTS => crate::command_return::success(),
28+
SERVO_COUNT => crate::command_return::success_u32(NUM_SERVO as u32),
29+
SET_ANGLE => {
30+
if servo_index >= NUM_SERVO as u32 {
31+
crate::command_return::failure(ErrorCode::NoDevice)
32+
} else if angle <= 180 {
33+
self.servo[servo_index as usize].set(angle as u16);
34+
crate::command_return::success()
35+
} else {
36+
crate::command_return::failure(ErrorCode::Fail)
37+
}
38+
}
39+
// Return the current angle.
40+
GET_ANGLE => {
41+
if servo_index >= NUM_SERVO as u32 {
42+
crate::command_return::failure(ErrorCode::NoDevice)
43+
} else {
44+
let angle = self.servo[servo_index as usize].get();
45+
crate::command_return::success_u32(angle as u32)
46+
}
47+
}
48+
_ => crate::command_return::failure(ErrorCode::NoSupport),
49+
}
50+
}
51+
}
52+
53+
#[cfg(test)]
54+
mod tests;
55+
56+
// -----------------------------------------------------------------------------
57+
// Implementation details below
58+
// -----------------------------------------------------------------------------
59+
60+
const DRIVER_NUM: u32 = 0x90009;
61+
62+
// Command numbers
63+
const EXISTS: u32 = 0;
64+
const SERVO_COUNT: u32 = 1;
65+
const SET_ANGLE: u32 = 2;
66+
const GET_ANGLE: u32 = 3;

unittest/src/fake/servo/tests.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use crate::fake;
2+
use fake::servo::*;
3+
use libtock_platform::CommandReturn;
4+
5+
// Tests the command implementation.
6+
#[test]
7+
fn command() {
8+
use fake::SyscallDriver;
9+
let servo = Servo::<1>::new();
10+
let value = servo.command(EXISTS, 0, 0);
11+
assert!(CommandReturn::is_success(&value));
12+
assert_eq!(
13+
CommandReturn::get_success_u32(&servo.command(SERVO_COUNT, 0, 0)),
14+
Some(1)
15+
);
16+
assert!(CommandReturn::is_success(&servo.command(SET_ANGLE, 0, 90)),);
17+
assert_eq!(
18+
CommandReturn::get_success_u32(&servo.command(GET_ANGLE, 0, 0)),
19+
Some(90)
20+
);
21+
}
22+
23+
#[test]
24+
fn kernel_integration() {
25+
use libtock_platform::Syscalls;
26+
let kernel = fake::Kernel::new();
27+
let servo = Servo::<1>::new();
28+
kernel.add_driver(&servo);
29+
let value = fake::Syscalls::command(DRIVER_NUM, EXISTS, 0, 0);
30+
assert!(CommandReturn::is_success(&value));
31+
assert_eq!(
32+
CommandReturn::get_success_u32(&fake::Syscalls::command(DRIVER_NUM, SERVO_COUNT, 0, 0)),
33+
Some(1)
34+
);
35+
assert_eq!(
36+
fake::Syscalls::command(DRIVER_NUM, SET_ANGLE, 1, 90).get_failure(),
37+
Some(ErrorCode::NoDevice)
38+
);
39+
assert_eq!(
40+
fake::Syscalls::command(DRIVER_NUM, SET_ANGLE, 0, 181).get_failure(),
41+
Some(ErrorCode::Fail)
42+
);
43+
assert!(fake::Syscalls::command(DRIVER_NUM, SET_ANGLE, 0, 90).is_success());
44+
assert_eq!(
45+
fake::Syscalls::command(DRIVER_NUM, GET_ANGLE, 0, 0).get_success_u32(),
46+
Some(90)
47+
);
48+
assert_eq!(
49+
fake::Syscalls::command(DRIVER_NUM, GET_ANGLE, 2, 0).get_failure(),
50+
Some(ErrorCode::NoDevice)
51+
);
52+
}

0 commit comments

Comments
 (0)