diff --git a/src/lib.rs b/src/lib.rs index 9837ab5a..430d88f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -115,6 +115,9 @@ pub mod msg; #[cfg(riot_module_periph_spi)] pub mod spi; +#[cfg(riot_module_periph_uart)] +pub mod uart; + #[cfg(riot_module_periph_adc)] pub mod adc; diff --git a/src/uart.rs b/src/uart.rs new file mode 100644 index 00000000..74f9217f --- /dev/null +++ b/src/uart.rs @@ -0,0 +1,375 @@ +//! Access to [RIOT's UART](https://doc.riot-os.org/group__drivers__periph__uart.html) +//! +//! Author: Kilian Barning + +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 { + const _ENODEV: isize = -(ENODEV as isize); + const _ENOTSUP: isize = -(ENOTSUP as isize); + match n.number { + _ENODEV => Self::InvalidDevice, + _ENOTSUP => Self::UnsupportedConfig, + _ => Self::Other, + } + } +} + +#[cfg(riot_module_periph_uart_modecfg)] +#[derive(Debug)] +#[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 + 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` where `RMain` is the value returned by the scoped main function + /// or a `Err` containing the error + /// + /// This is the scoped version of [`new()`] 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| { + /// //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:?}")); + /// ``` + pub fn new_scoped<'scope, F, Main, RMain>( + index: usize, + baud: u32, + user_callback: &'scope mut F, + main: Main, + ) -> Result + where + F: FnMut(u8) + Send + 'scope, + Main: FnOnce(&mut Self) -> RMain, + { + // 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` if the UART was initialized successfully or a + /// `Err` containing the error. As the name implies, the created `UART` device can ONLY 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 { + 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` if the UART + /// was initialized successfully or a `Err` 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| { + /// //do something here with the received 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( + index: usize, + baud: u32, + user_callback: &'static mut F, + ) -> Result + 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 + /// # 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) { + 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::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::from_c(unsafe { uart_pin_tx(self.dev) }) + } + + /// Configure the function that will be called when a start condition is detected + /// This will not enable / disable the generation of the RX start interrupt + /// # Arguments + /// * `user_fxopt` - The user defined callback function that gets called when a start condition is detected + #[cfg(riot_module_periph_uart_rxstart_irq)] + pub fn rxstart_irq_configure(&mut self, user_fxopt: &'a mut F) + where + F: FnMut() + Send + 'static, + { + unsafe { + uart_rxstart_irq_configure( + dev, + Self::rxstart_callback::, + user_fxopt as *mut _ as *mut c_void, + ) + }; + } + + /// Enable the RX start interrupt + #[cfg(riot_module_periph_uart_rxstart_irq)] + pub fn rxstart_irq_enable(&mut self) { + unsafe { uart_rxstart_irq_enable(self.dev) }; + } + + /// Disable the RX start interrupt + #[cfg(riot_module_periph_uart_rxstart_irq)] + pub fn rxstart_irq_disable(&mut self) { + unsafe { uart_rxstart_irq_disable(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 + } + + /// This is the callback that gets called directly from the kernel when a start condition is detected + /// # Arguments + /// * `user_callback` - The address pointing to the user defined callback + #[cfg(riot_module_periph_uart_rxstart_irq)] + unsafe extern "C" fn rxstart_callback(user_callback: *mut c_void) + where + F: FnMut() + 'scope, + { + (*(user_callback as *mut F))(); // 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(); + } + } +} diff --git a/tests/uart/Cargo.toml b/tests/uart/Cargo.toml new file mode 100644 index 00000000..03b4901f --- /dev/null +++ b/tests/uart/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "riot-wrappers-test-uart" +version = "0.1.0" +authors = ["Kilian Barning "] +edition = "2021" +publish = false + +[lib] +crate-type = ["staticlib"] + +[profile.release] +panic = "abort" + +[dependencies] +riot-wrappers = { version = "*", features = [ "set_panic_handler" ] } +riot-sys = "*" +embedded-hal = "0.2.4" + +[patch.crates-io] +riot-sys = { git = "https://github.com/RIOT-OS/rust-riot-sys" } diff --git a/tests/uart/Makefile b/tests/uart/Makefile new file mode 100644 index 00000000..55f95e98 --- /dev/null +++ b/tests/uart/Makefile @@ -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 diff --git a/tests/uart/src/lib.rs b/tests/uart/src/lib.rs new file mode 100644 index 00000000..cc6cedcb --- /dev/null +++ b/tests/uart/src/lib.rs @@ -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:?}")); +}