Skip to content

Commit

Permalink
Add static_buffer!() macro to help embedded users.
Browse files Browse the repository at this point in the history
  • Loading branch information
de-vri-es committed Jan 1, 2025
1 parent 351e4ed commit 79ab7da
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 21 deletions.
75 changes: 75 additions & 0 deletions src/bus/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,44 @@ pub type DefaultBuffer = alloc::vec::Vec<u8>;
#[cfg(not(feature = "alloc"))]
pub type DefaultBuffer = &'static mut [u8];

/// Create a mutable static buffer of size N.
///
/// This macro returns a `&'mut static [u8]`.
/// Each occurence of the macro may only be evaluated once,
/// any subsequent invocation will panic.
///
/// The macro is usable as expression in const context.
///
/// # Usage:
/// ```no_run
/// # fn main() -> Result<(), std::io::Error> {
/// # let serial_port = serial2::SerialPort::open("/dev/null", 57600)?;
/// use dynamixel2::{Client, static_buffer};
/// let client = Client::with_buffers(serial_port, static_buffer!(128), static_buffer!(64))?;
/// # Ok(())
/// # }
/// ```
#[macro_export]
macro_rules! static_buffer {
($N:literal) => {
{
use ::core::sync::atomic::{AtomicBool, Ordering};
static USED: AtomicBool = AtomicBool::new(false);
static mut BUFFER: [u8; $N] = [0; $N];
if USED.swap(true, Ordering::Relaxed) {
panic!("static buffer already used, each occurence of `static_buffer!()` may only be evaluated once");
}
unsafe {
// Use raw pointer to avoid compiler warning.
let buffer = &raw mut BUFFER;
// Convert to reference in separate expression to avoid clippy warning.
let buffer = &mut *buffer;
buffer.as_mut_slice()
}
}
};
}

/// Low level interface to a DYNAMIXEL Protocol 2.0 bus.
///
/// Does not assume anything about the direction of communication.
Expand Down Expand Up @@ -387,4 +425,41 @@ mod test {
assert!(find_header(&[0xFF, 1]) == 2);
assert!(find_header(&[0, 1, 2, 3, 4, 0xFF, 6]) == 7);
}

#[test]
fn test_static_buffer() {
let buffer1 = static_buffer!(128);
assert!(buffer1.len() == 128);
for i in 0..buffer1.len() {

Check warning on line 433 in src/bus/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

the loop variable `i` is only used to index `buffer1`

warning: the loop variable `i` is only used to index `buffer1` --> src/bus/mod.rs:433:12 | 433 | for i in 0..buffer1.len() { | ^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop = note: `#[warn(clippy::needless_range_loop)]` on by default help: consider using an iterator | 433 | for <item> in &buffer1 { | ~~~~~~ ~~~~~~~~
assert!(buffer1[i] == 0);
}

let buffer2 = static_buffer!(64);
assert!(buffer2.len() == 64);
for i in 0..buffer2.len() {

Check warning on line 439 in src/bus/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

the loop variable `i` is only used to index `buffer2`

warning: the loop variable `i` is only used to index `buffer2` --> src/bus/mod.rs:439:12 | 439 | for i in 0..buffer2.len() { | ^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop help: consider using an iterator | 439 | for <item> in &buffer2 { | ~~~~~~ ~~~~~~~~
assert!(buffer2[i] == 0);
}
}

#[test]
#[should_panic]
fn test_static_buffer_panics_when_evaluated_in_loop() {
for _ in 0..2 {
let buffer = static_buffer!(128);
assert!(buffer.len() == 128);
}
}

#[test]
#[should_panic]
fn test_static_buffer_panics_when_evaluated_in_loop_through_function() {
fn make_buffer() -> &'static mut [u8] {
static_buffer!(128)
}

for _ in 0..2 {
let buffer = make_buffer();
assert!(buffer.len() == 128);
}
}
}
41 changes: 29 additions & 12 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,34 @@ use crate::bus::{Bus, StatusPacket};
use crate::instructions::instruction_id;
use crate::{ReadError, TransferError, WriteError};


/// Client for the Dynamixel Protocol 2 communication.
///
/// Used to interact with devices on the bus.
pub struct Client<SerialPort, Buffer = crate::bus::DefaultBuffer>
where
SerialPort: crate::SerialPort,
Buffer: AsRef<[u8]> + AsMut<[u8]>,
{
bus: Bus<SerialPort, Buffer>,
macro_rules! make_client_struct {
($($DefaultSerialPort:ty)?) => {
/// Client for the Dynamixel Protocol 2 communication.
///
/// Used to interact with devices on the bus.
///
/// If the `"serial2"` feature is enabled, the `SerialPort` generic type argument defaults to [`serial2::SerialPort`].
/// If it is not enabled, the `SerialPort` argument must always be specified.
///
/// The `Buffer` generic type argument defaults to `Vec<u8>` if the `"alloc"` feature is enabled,
/// and to `&'static mut [u8]` otherwise.
/// See the [`static_buffer!()`] macro for a way to safely create a mutable static buffer.
pub struct Client<SerialPort $(= $DefaultSerialPort)?, Buffer = crate::bus::DefaultBuffer>
where
SerialPort: crate::SerialPort,
Buffer: AsRef<[u8]> + AsMut<[u8]>,
{
bus: Bus<SerialPort, Buffer>,
}
};
}

#[cfg(feature = "serial2")]
make_client_struct!(serial2::SerialPort);

#[cfg(not(feature = "serial2"))]
make_client_struct!();

impl<SerialPort, Buffer> core::fmt::Debug for Client<SerialPort, Buffer>
where
SerialPort: crate::SerialPort + core::fmt::Debug,
Expand Down Expand Up @@ -118,9 +135,9 @@ where
Ok(Self { bus })
}

/// Get a reference to the underlying [`SerialPort`].
/// Get a reference to the underlying serial port.
///
/// Note that performing any read or write with the [`SerialPort`] bypasses the read/write buffer of the bus,
/// Note that performing any read or write to the serial port bypasses the read/write buffer of the bus,
/// and may disrupt the communication with the motors.
/// In general, it should be safe to read and write to the bus manually in between instructions,
/// if the response from the motors has already been received.
Expand Down
35 changes: 26 additions & 9 deletions src/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,32 @@ use crate::bus::{Bus, InstructionPacket};
use crate::{InvalidParameterCount, ReadError, WriteError};
use core::time::Duration;

/// Dynamixel [`Device`] for implementing the device side of the DYNAMIXEL Protocol 2.0.
pub struct Device<SerialPort, Buffer = crate::bus::DefaultBuffer>
where
SerialPort: crate::SerialPort,
Buffer: AsRef<[u8]> + AsMut<[u8]>,
{
bus: Bus<SerialPort, Buffer>,
macro_rules! make_device_struct {
($($DefaultSerialPort:ty)?) => {
/// Dynamixel [`Device`] for implementing the device side of the DYNAMIXEL Protocol 2.0.
///
/// If the `"serial2"` feature is enabled, the `SerialPort` generic type argument defaults to [`serial2::SerialPort`].
/// If it is not enabled, the `SerialPort` argument must always be specified.
///
/// The `Buffer` generic type argument defaults to `Vec<u8>` if the `"alloc"` feature is enabled,
/// and to `&'static mut [u8]` otherwise.
/// See the [`static_buffer!()`] macro for a way to safely create a mutable static buffer.
pub struct Device<SerialPort $(= $DefaultSerialPort)?, Buffer = crate::bus::DefaultBuffer>
where
SerialPort: crate::SerialPort,
Buffer: AsRef<[u8]> + AsMut<[u8]>,
{
bus: Bus<SerialPort, Buffer>,
}
};
}

#[cfg(feature = "serial2")]
make_device_struct!(serial2::SerialPort);

#[cfg(not(feature = "serial2"))]
make_device_struct!();

impl<SerialPort, Buffer> core::fmt::Debug for Device<SerialPort, Buffer>
where
SerialPort: crate::SerialPort + core::fmt::Debug,
Expand Down Expand Up @@ -110,9 +127,9 @@ where
Ok(Device { bus })
}

/// Get a reference to the underlying [`SerialPort`].
/// Get a reference to the underlying serial port.
///
/// Note that performing any read or write with the [`SerialPort`] bypasses the read/write buffer of the device,
/// Note that performing any read or write to the serial port bypasses the read/write buffer of the device,
/// and may disrupt the communication with the motors.
/// In general, it should be safe to read and write to the device manually in between instructions,
/// if the response from the motors has already been received.
Expand Down

0 comments on commit 79ab7da

Please sign in to comment.