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

UART: Add wrapper around RIOT's UART-interface #39

Open
wants to merge 43 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
9722813
Added uart
kbarning Feb 1, 2023
5ace203
Fixed callback
kbarning Feb 1, 2023
530e441
Fix code according to @jaschman
kbarning Feb 6, 2023
14f0d08
Add init pins
kbarning Feb 6, 2023
3d7b15b
Fix comments
kbarning Feb 6, 2023
5fb2e12
Fixed signature
kbarning Feb 8, 2023
e1dd2c0
Fixed closure type
kbarning Feb 8, 2023
ac0d680
Mark init pins as unsafe
kbarning Feb 8, 2023
a39a7a7
Fix comments
kbarning Feb 8, 2023
1a0d07c
Change drop
kbarning Feb 8, 2023
b62e14e
Introduce Phantom Data Lifetimes
kbarning Feb 9, 2023
788d633
Add generics to rxstart
kbarning Feb 9, 2023
7e2badb
Add mode feature
kbarning Feb 9, 2023
eec16de
PWM: Add wrapper around RIOTs PWM-interface
Feb 8, 2023
398cf41
Removed wrong libs
kbarning Feb 9, 2023
77290d9
Delete pwm.rs
kbarning Feb 9, 2023
2adc6ac
Removed unused comments
kbarning Feb 10, 2023
a3ee384
Update uart.rs
kbarning Feb 10, 2023
6ca86f3
Fixed issues as suggested by chrysn
kbarning Feb 15, 2023
2942733
Added new macro to init uart
kbarning Feb 19, 2023
bf83576
Fix comments
kbarning Feb 19, 2023
06ba80e
Added scoped approach
kbarning Feb 19, 2023
2788caa
Add static new
kbarning Feb 19, 2023
a545c7b
Added new static impl + fix doc
kbarning Feb 19, 2023
e0e99a6
Make get gpio optional
kbarning Feb 27, 2023
820b42c
Remove colission detection for now
kbarning Feb 27, 2023
74e68fd
Add new scoped main
kbarning Mar 17, 2023
be20cea
Update src/uart.rs
kbarning Mar 21, 2023
3a9e145
Update src/uart.rs
kbarning Mar 21, 2023
d957c9f
Update src/uart.rs
kbarning Mar 21, 2023
e45c297
Update src/uart.rs
kbarning Mar 21, 2023
7aa931b
Update src/uart.rs
kbarning Mar 21, 2023
762a639
Make internal construct fn unsafe
kbarning Mar 21, 2023
748fe93
add test
kbarning Mar 21, 2023
cd066aa
Fix scope signature and add cfg to unused structs
kbarning Mar 21, 2023
12fda60
Reordering
kbarning Mar 21, 2023
973d70a
Cleanup
kbarning Mar 21, 2023
59f3d78
Update tests/uart/Cargo.toml
kbarning Dec 7, 2024
03595f9
Update src/uart.rs
kbarning Dec 7, 2024
9763bf3
Update src/uart.rs
kbarning Dec 7, 2024
2b63e71
Update tests/uart/Cargo.toml
kbarning Dec 7, 2024
455291e
Add println to examples, remove unused code
kbarning Dec 7, 2024
16a67e9
Fix rebase
kbarning Dec 7, 2024
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
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,6 @@ pub mod auto_init;
feature = "with_embedded_nal_async"
))]
mod async_helpers;

#[cfg(riot_module_periph_uart)]
pub mod uart;
331 changes: 331 additions & 0 deletions src/uart.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@
//! Access to [RIOT's UART](https://doc.riot-os.org/group__drivers__periph__uart.html)
//!
//! Author: Kilian Barning <[email protected]>

use core::ptr;

use crate::error::{NegativeErrorExt, NumericError};
use riot_sys::libc::{c_uint, c_void};
use riot_sys::*;

/// This enum representatives the status returned by various `UART`-functions
#[derive(Debug)]
#[non_exhaustive]
pub enum UartDeviceError {
InvalidDevice,
UnsupportedConfig,
Other,
}

impl UartDeviceError {
/// Converts the given `c_int` into the matching Enum representation
fn from_c(n: NumericError) -> Self {
match n {
crate::error::ENODEV => Self::InvalidDevice,
crate::error::ENOTSUP => Self::UnsupportedConfig,
_ => Self::Other,
}
}
}

#[cfg(riot_module_periph_uart_modecfg)]
#[derive(Debug)]
chrysn marked this conversation as resolved.
Show resolved Hide resolved
#[non_exhaustive]
pub enum DataBits {
Five,
Six,
Seven,
Eight,
}

#[cfg(riot_module_periph_uart_modecfg)]
impl DataBits {
fn to_c(self) -> uart_data_bits_t {
match self {
Self::Five => uart_data_bits_t_UART_DATA_BITS_5,
Self::Six => uart_data_bits_t_UART_DATA_BITS_6,
Self::Seven => uart_data_bits_t_UART_DATA_BITS_7,
Self::Eight => uart_data_bits_t_UART_DATA_BITS_8,
}
}
}

#[cfg(riot_module_periph_uart_modecfg)]
#[derive(Debug)]
#[non_exhaustive]
pub enum Parity {
None,
Even,
Odd,
Mark,
Space,
}

#[cfg(riot_module_periph_uart_modecfg)]
impl Parity {
fn to_c(self) -> uart_parity_t {
match self {
Self::None => uart_parity_t_UART_PARITY_NONE,
Self::Even => uart_parity_t_UART_PARITY_EVEN,
Self::Odd => uart_parity_t_UART_PARITY_ODD,
Self::Mark => uart_parity_t_UART_PARITY_MARK,
Self::Space => uart_parity_t_UART_PARITY_SPACE,
}
}
}

#[cfg(riot_module_periph_uart_modecfg)]
#[derive(Debug)]
#[non_exhaustive]
pub enum StopBits {
One,
Two,
}

#[cfg(riot_module_periph_uart_modecfg)]
impl StopBits {
fn to_c(self) -> uart_stop_bits_t {
match self {
Self::One => uart_stop_bits_t_UART_STOP_BITS_1,
Self::Two => uart_stop_bits_t_UART_STOP_BITS_2,
}
}
}

/// This struct contains the `UART` device and handles all operation regarding it
///
/// [UART implementation]: https://doc.riot-os.org/group__drivers__periph__uart.html
#[derive(Debug)]
pub struct UartDevice {
dev: uart_t,
}

impl UartDevice {
/// Unsafety: To use this safely, the caller must ensure that the returned Self is destructed before &'scope mut F becomes unavailable.
unsafe fn construct_uart<'scope, F>(
index: usize,
baud: u32,
user_callback: &'scope mut F,
) -> Result<Self, UartDeviceError>
where
F: FnMut(u8) + Send + 'scope,
{
let dev = macro_UART_DEV(index as c_uint);
uart_init(
dev,
baud,
Some(Self::new_data_callback::<'scope, F>),
user_callback as *mut _ as *mut c_void,
)
.negative_to_error()
.map(|_| Self { dev })
.map_err(UartDeviceError::from_c)
}

/// Tries to initialize the given `UART`. Returns a Result with rather `Ok<RMain>` where `RMain` is the value returned by the scoped main function
/// or a `Err<UartDeviceStatus>` containing the error
///
/// This is the scoped version of [`new_with_static_cb()`] that can be used if you want to use short-lived callbacks, such as
/// closures or anything containing references. The UartDevice is deconfigured when the internal main function
/// terminates. A common pattern around this kind of scoped functions is that `main` contains the application's
/// main loop, and never terminates (in which case the clean-up code is eliminated during compilation).
/// # Arguments
///
/// * `dev` - The index of the hardware device
/// * `baud` - The used baud rate
/// * `user_callback` The user defined callback that gets called from the os whenever new data is received from the `UART`
/// * `main` The mainloop that is executed inside the wrapper
///
/// # Examples
/// ```
/// use riot_wrappers::uart::UartDevice;
/// let mut cb = |new_data| {
/// println!("Received {:02x}", new_data);
/// };
/// let mut scoped_main = |self_: &mut UartDevice| loop {
/// self_.write(b"Hello from UART")
/// };
/// let mut uart = UartDevice::new_scoped(0, 115200, &mut cb, scoped_main)
/// .unwrap_or_else(|e| panic!("Error initializing UART: {e:?}"));
/// ```
pub fn new_scoped<'scope, F, Main, RMain>(
index: usize,
baud: u32,
user_callback: &'scope mut F,
main: Main,
) -> Result<RMain, UartDeviceError>
where
F: FnMut(u8) + Send + 'scope,
Main: FnOnce(&mut Self) -> RMain,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, just found a soundness hole in this:

A user might do something like this:

new_scoped(…, |_char| (), |self_| {
    let racy = 0;
    let shortlived_callback = |_char| { *racy = char };
    new_scoped(…, shortlived_callback, |self2_| {
        core::mem::swap(self_, self2_);
    });
    // at drop time it's effectively self_ that is dropped
    do_something_with(&mut racy);
    // wait for  input
    // goes to self2_, calls shortlived_callback, writes into racy
});

The two classical workarounds that occur to me off my head are using Pin (which we don't fully need but resolves the issue because they can't be swapped), or making 'scope part of Self (where for _without_rx and _static that'd be 'static), and then Main is for<'a> Main: FnOnce(&mut UartDevice<'a>) so it becomes a brand, and this module will by construction ensure that there are never two UartDevice that can be swapped.

A scope was even in the type back in #39 (comment), which from how I view it now was right, but we just didn't manage to figure out why it was. The new_scoped etc. types could still live on the <'static> version of this, which should make it unambiguous for both the user and the compiler which to pick.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, which version would you recommend? I have to admit, I've reached the end of my knowledge here.

Copy link
Member

@chrysn chrysn Dec 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the version currently on chrysn-impl_uart_wrapper would work. That the example still works unmodified even though I altered the lifetimes accordingly is a good sign.

[edit: Oups, that one so far does not apply the branding; I'm redoing it…]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed now: The proposed 479d663 introduces a lifetime on a UART, which states that if any callback has been configured, that callback is valid for that lifetime. Constructors that don't set a callback (or the one that sets one for a shorter time) are implemented on the 'static because that's what corresponds to "no callback has been registered (therefore all registered callbacks live for the longest possible time)".

{
// This possibly relies on Rust code in RIOT to not unwind.
let mut self_ = unsafe { Self::construct_uart(index, baud, user_callback) }?;
let result = (main)(&mut self_);
drop(self_);
Ok(result)
}

/// Tries to initialize the given `UART`. Returns a Result with rather `Ok<Self>` if the UART was initialized successfully or a
/// `Err<UartDeviceStatus>` containing the error. As the name implies, the created `UART` device can <b>ONLY</b> send data
///
/// # Arguments
///
/// * `dev` - The index of the hardware device
/// * `baud` - The used baud rate
///
/// # Examples
/// ```
/// use riot_wrappers::uart::UartDevice;
/// let mut uart = UartDevice::new_without_rx(0, 115200)
/// .unwrap_or_else(|e| panic!("Error initializing UART: {e:?}"));
/// uart.write(b"Hello from UART");
/// ```
pub fn new_without_rx(index: usize, baud: u32) -> Result<Self, UartDeviceError> {
unsafe {
let dev = macro_UART_DEV(index as c_uint);
uart_init(dev, baud, None, ptr::null_mut())
.negative_to_error()
.map(|_| Self { dev })
.map_err(UartDeviceError::from_c)
}
}

/// Tries to initialize the given `UART` with a static callback. Returns a Result with rather `Ok<Self>` if the UART
/// was initialized successfully or a `Err<UartDeviceStatus>` containing the error
///
/// # Arguments
///
/// * `dev` - The index of the hardware device
/// * `baud` - The used baud rate
/// * `user_callback` The user defined callback that gets called from the os whenever new data is received from the `UART`
///
/// # Examples
/// ```
/// use riot_wrappers::uart::UartDevice;
/// static mut CB: fn(u8) = |new_data| {
/// println!("Received {:02x}", new_data);
/// };
/// let mut uart = UartDevice::new_with_static_cb(0, 115200, unsafe { &mut CB })
/// .unwrap_or_else(|e| panic!("Error initializing UART: {e:?}"));
/// uart.write(b"Hello from UART");
/// ```
pub fn new_with_static_cb<F>(
index: usize,
baud: u32,
user_callback: &'static mut F,
) -> Result<Self, UartDeviceError>
where
F: FnMut(u8) + Send + 'static,
{
unsafe { Self::construct_uart(index, baud, user_callback) }
}

/// Sets the mode according to the given parameters
/// Should the parameters be invalid, the function returns a Err<UartDeviceStatus::UnsupportedConfig>
/// # Arguments
/// * `data_bits` - Number of data bits in a UART frame
/// * `parity` - Parity mode
/// * `stop_bits` - Number of stop bits in a UART frame
///
/// # Examples
/// ```
/// use riot_wrappers::uart::{DataBits, Parity, StopBits, UartDevice};
/// let mut uart = UartDevice::new_without_rx(0, 115200)
/// .unwrap_or_else(|e| panic!("Error initializing UART: {e:?}"));
/// uart.set_mode(DataBits::Eight, Parity::None, StopBits::One)
/// .unwrap_or_else(|e| panic!("Error setting UART mode: {e:?}"));
/// ```
#[cfg(riot_module_periph_uart_modecfg)]
pub fn set_mode(
&mut self,
data_bits: DataBits,
parity: Parity,
stop_bits: StopBits,
) -> Result<(), UartDeviceError> {
unsafe {
match UartDeviceError::from_c(uart_mode(
self.dev,
data_bits.to_c(),
parity.to_c(),
stop_bits.to_c(),
)) {
UartDeviceError::Success => Ok(()),
status => Err(status),
}
}
}

/// Transmits the given data via the `UART`-device
///
/// # Examples
/// ```
/// use riot_wrappers::uart::UartDevice;
/// let mut uart = UartDevice::new_without_rx(0, 115200)
/// .unwrap_or_else(|e| panic!("Error initializing UART: {e:?}"));
/// uart.write(b"Hello from UART\n");
/// ```
pub fn write(&mut self, data: &[u8]) {
unsafe {
uart_write(self.dev, data.as_ptr(), data.len() as size_t);
}
}

/// Turns on the power from the `UART-Device`
pub fn power_on(&mut self) {
unsafe { uart_poweron(self.dev) };
}

/// Turns off the power from the `UART-Device`
pub fn power_off(&mut self) {
unsafe { uart_poweroff(self.dev) };
}

/// This function normally does not need to be called. But in some case, the pins on the `UART`
/// might be shared with some other functionality (like `GPIO`). In this case, it is necessary
/// to give the user the possibility to init the pins again.
#[cfg(riot_module_periph_uart_reconfigure)]
pub unsafe fn init_pins(&mut self) {
uart_init_pins(self.dev);
}

/// Change the pins back to plain GPIO functionality
#[cfg(riot_module_periph_uart_reconfigure)]
pub unsafe fn deinit_pins(&mut self) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When is this safe to call?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'm not so sure about this. Is it an option to consume the object here via taking self, so that the is destructed, and the user can use the pins as normal GPIO?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the object is consumed, it can't be reinited with the same settings later; it's an option, but I don't think that's the intention of deinit_pins. That seems to be more of a "let's pause this for a moment, do funky stuff with the UART, and return to using it later".

If we're not sure (I am not either), maybe let's remove that API for now.

If we keep this and the other removals in a separate commit when squashing, it'll be easy to selectively revert it.

uart_deinit_pins(self.dev);
}

/// Get the RX pin
#[cfg(riot_module_periph_uart_reconfigure)]
pub fn get_pin_rx(&mut self) -> Option<crate::gpio::GPIO> {
crate::gpio::GPIO::from_c(unsafe { uart_pin_rx(self.dev) })
}

/// Get the TX pin
#[cfg(riot_module_periph_uart_reconfigure)]
pub fn get_pin_tx(&mut self) -> Option<crate::gpio::GPIO> {
crate::gpio::GPIO::from_c(unsafe { uart_pin_tx(self.dev) })
}

/// This is the callback that gets called directly from the kernel if new data from the `UART` is received
/// # Arguments
/// * `user_callback` - The address pointing to the user defined callback
/// * `data` - The newly received data from the `UART`
unsafe extern "C" fn new_data_callback<'scope, F>(user_callback: *mut c_void, data: u8)
where
F: FnMut(u8) + 'scope,
{
(*(user_callback as *mut F))(data); // We cast the void* back to the closure and call it
}
}

impl Drop for UartDevice {
/// The `drop` method resets the `UART`, removes the interrupt and tries
/// to reset the `GPIO` pins if possible
fn drop(&mut self) {
unsafe {
uart_init(self.dev, 9600, None, ptr::null_mut());
#[cfg(riot_module_periph_uart_reconfigure)]
self.deinit_pins();
}
}
}
17 changes: 17 additions & 0 deletions tests/uart/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "riot-wrappers-test-uart"
version = "0.1.0"
authors = ["Kilian Barning <[email protected]>"]
edition = "2021"
publish = false

[lib]
crate-type = ["staticlib"]

[profile.release]
panic = "abort"

[dependencies]
riot-wrappers = { path = "../..", features = [ "set_panic_handler" ] }
riot-sys = "*"
embedded-hal = "0.2.4"
8 changes: 8 additions & 0 deletions tests/uart/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# name of your application
APPLICATION = riot-wrappers-test-uart
APPLICATION_RUST_MODULE = riot-wrappers-test-uart
BASELIBS += $(APPLICATION_RUST_MODULE).module
FEATURES_REQUIRED += rust_target
FEATURES_REQUIRED += periph_uart

include $(RIOTBASE)/Makefile.include
18 changes: 18 additions & 0 deletions tests/uart/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#![no_std]

use riot_wrappers::println;
use riot_wrappers::riot_main;
use riot_wrappers::uart;

riot_main!(main);

fn main() {
let mut cb = |new_data| {
//do something here with the received data
};
let mut scoped_main = |self_: &mut UartDevice| loop {
self_.write(b"Hello from UART")
};
let mut uart = UartDevice::new_scoped(0, 115200, &mut cb, scoped_main)
.unwrap_or_else(|e| panic!("Error initializing UART: {e:?}"));
}
Loading