From 46da34b2879d97edceea253f38c4c0dfaa20bd1a Mon Sep 17 00:00:00 2001 From: Wilfred Mallawa Date: Wed, 22 Nov 2023 13:05:29 +1000 Subject: [PATCH 1/2] apis: i2c_master_slave: add support Adds support to the i2c master slave sycall api for libtock-rs. The current implementation implements all the functionality synchronously. This api was testing using 2 Particle Boron boards (nrf52840 based) setup as a master and slave device. Additionally, this api was also tested by communicating with an MCP9808 sensor as an I2C master, again using the Particle Boron. Signed-off-by: Wilfred Mallawa --- Cargo.toml | 2 + apis/i2c_master_slave/Cargo.toml | 16 ++ apis/i2c_master_slave/src/lib.rs | 436 +++++++++++++++++++++++++++++++ src/lib.rs | 4 + 4 files changed, 458 insertions(+) create mode 100644 apis/i2c_master_slave/Cargo.toml create mode 100644 apis/i2c_master_slave/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 808c4349..4bb23d2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ libtock_buzzer = {path = "apis/buzzer"} libtock_console = { path = "apis/console" } libtock_debug_panic = { path = "panic_handlers/debug_panic" } libtock_gpio = { path = "apis/gpio" } +libtock_i2c_master_slave = { path = "apis/i2c_master_slave" } libtock_leds = { path = "apis/leds" } libtock_low_level_debug = { path = "apis/low_level_debug" } libtock_ninedof = { path = "apis/ninedof" } @@ -58,6 +59,7 @@ members = [ "apis/buzzer", "apis/console", "apis/gpio", + "apis/i2c_master_slave", "apis/leds", "apis/low_level_debug", "apis/ninedof", diff --git a/apis/i2c_master_slave/Cargo.toml b/apis/i2c_master_slave/Cargo.toml new file mode 100644 index 00000000..e2333f84 --- /dev/null +++ b/apis/i2c_master_slave/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "libtock_i2c_master_slave" +version = "0.1.0" +authors = [ + "Tock Project Developers ", + "Wilfred Mallawa ", +] +license = "Apache-2.0 OR MIT" +edition = "2021" +repository = "https://www.github.com/tock/libtock-rs" +rust-version.workspace = true +description = "libtock I2C master-slave driver" + +[dependencies] +libtock_platform = { path = "../../platform" } + diff --git a/apis/i2c_master_slave/src/lib.rs b/apis/i2c_master_slave/src/lib.rs new file mode 100644 index 00000000..c2d3c8c4 --- /dev/null +++ b/apis/i2c_master_slave/src/lib.rs @@ -0,0 +1,436 @@ +#![no_std] + +use core::cell::Cell; +use libtock_platform as platform; +use libtock_platform::allow_ro::AllowRo; +use libtock_platform::allow_rw::AllowRw; +use libtock_platform::share; +use libtock_platform::subscribe::Subscribe; +use libtock_platform::{DefaultConfig, ErrorCode, Syscalls}; + +pub struct I2CMasterSlave(S, C); + +impl I2CMasterSlave { + /// # Summary + /// + /// Perform an I2C write to the slave device on @addr. + /// + /// # Parameter + /// + /// * `addr`: Slave device address + /// * `buf`: Storage buffer, this should be bigger than @len + /// * `len`: Number of bytes to write from @buf + /// + /// # Returns + /// On success: Returns Ok(()), @len bytes were written from @buf. + /// On failure: Err(ErrorCode), with failure ErrorCode. + pub fn i2c_master_slave_write_sync( + addr: u16, + buffer: &[u8], + len: u16, + ) -> Result<(), ErrorCode> { + // We could write just the buffer length, but this may lead to + // ambiguities for the caller. So Err out early. + if len as usize > buffer.len() { + return Err(ErrorCode::NoMem); + } + let called: Cell> = Cell::new(None); + // The kernel will split this argument into upper length and lower address. + let cmd_arg0: u32 = (len as u32) << 16 | addr as u32; + share::scope::< + ( + AllowRo<_, DRIVER_NUM, { ro_allow::MASTER_TX }>, + Subscribe<_, DRIVER_NUM, { subscribe::MASTER_WRITE }>, + ), + _, + _, + >(|handle| { + let (allow_ro, subscribe) = handle.split(); + + S::allow_ro::(allow_ro, buffer)?; + + S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::MASTER_WRITE }>(subscribe, &called)?; + + S::command(DRIVER_NUM, i2c_master_slave_cmd::MASTER_WRITE, cmd_arg0, 0).to_result()?; + + loop { + S::yield_wait(); + if let Some((r0, status, _r1)) = called.get() { + // Kernel uses a different cmd number for this... + assert_eq!(r0, 0); + return match status { + 0 => Ok(()), + e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), + }; + } + } + }) + } + + /// # Summary + /// + /// Perform an I2C read from the the slave device with the slave address of @addr. + /// + /// # Parameter + /// + /// * `addr`: Slave device address + /// * `buf`: Storage buffer, this should be bigger than @len + /// * `len`: Number of bytes to read into @buf + /// + /// # Returns + /// On success: Returns Ok(()) with @bytes_received valid. + /// On failure: Err(ErrorCode), Failure ErrorCode and @bytes_received is invalid. + /// + /// Note: @bytes_received is the first return tuple index (valid only on success). + pub fn i2c_master_slave_read_sync( + addr: u16, + buf: &mut [u8], + len: u16, + ) -> (usize, Result<(), ErrorCode>) { + if len as usize > buf.len() { + return (0, Err(ErrorCode::NoMem)); + } + // This is the total amount of bytes read if the operation was a success. + // Otherwise, it is invalid. + let mut bytes_received: usize = core::cmp::min(buf.len(), len as usize); + let called: Cell> = Cell::new(None); + // The kernel will split this argument into upper length and lower address. + let cmd_arg0: u32 = (len as u32) << 16 | addr as u32; + let r = share::scope::< + ( + AllowRw<_, DRIVER_NUM, { rw_allow::MASTER_RX }>, + Subscribe<_, DRIVER_NUM, { subscribe::MASTER_READ }>, + ), + _, + _, + >(|handle| { + let (allow_rw, subscribe) = handle.split(); + S::allow_rw::(allow_rw, buf)?; + S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::MASTER_READ }>(subscribe, &called)?; + // When this fails, `called` is guaranteed unmodified, + // because upcalls are never processed until we call `yield`. + S::command(DRIVER_NUM, i2c_master_slave_cmd::MASTER_READ, cmd_arg0, 0).to_result()?; + + loop { + S::yield_wait(); + if let Some((r0, _read_len, status)) = called.get() { + // TODO: The kernel I2C api does not currently return the read_len, so this + // will be invalid. We should keep track, likely assume the transfer was + // done if no error. See: tock@capsules/core/src/i2c_master_slave_driver.rs:129 + // see: https://github.com/tock/tock/issues/3735 + // Kernel uses a different cmd number for this... + assert_eq!(r0, 1); + return match status { + 0 => Ok(()), + e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), + }; + } + } + }); + // If the operation failed, make bytes received zero so that the caller isn't confused in case + // the error is not handled properly. That is, in case of an error, we cannot guarantee the + // number of bytes received. + if r.is_err() { + bytes_received = 0; + } + (bytes_received, r) + } + + /// # Summary + /// + /// Perform an I2C write followed by a read. + /// + /// Note: The kernel uses the TX buffer for both actions, such that if you request a + /// a read that exceeds the buffer length of @w_buf, the read will be + /// limited to the capacity of @w_buf. This API will detect such a case + /// and error to avoid ambiguities until we have a better solution in the kernel. + /// + /// # Parameter + /// + /// * `addr`: Slave device address + /// * `w_buf`: Write buffer + /// * `r_buf`: Read buffer + /// * `w_len`: Number of bytes to write from @w_buf + /// * `r_len`: Number of bytes to read into @r_buf + /// + /// # Returns + /// On success: Returns Ok(()) with @bytes_received valid. + /// On failure: Err(ErrorCode), Failure ErrorCode and @bytes_received is invalid. + /// + /// Note: @bytes_received is the first return tuple index (valid only on success). + pub fn i2c_master_slave_write_read_sync( + addr: u16, + w_buf: &mut [u8], + r_buf: &mut [u8], + w_len: u16, + r_len: u16, + ) -> (usize, Result<(), ErrorCode>) { + if w_len as usize > w_buf.len() || r_len as usize > r_buf.len() { + return (0, Err(ErrorCode::NoMem)); + } + // TODO: Kernel uses the TX Buffer to perform both RX/TX for a write_read, so if + // the @w_buff is smaller than @r_len. The subsequent read will stop prematurely. + // So let's error here until that is addressed. + if r_len as usize > w_buf.len() { + return (0, Err(ErrorCode::NoMem)); + } + // This is the total amount of bytes read if the operation was a success. + // Otherwise, it is invalid. + let mut bytes_received: usize = core::cmp::min(r_buf.len(), r_len as usize); + let called: Cell> = Cell::new(None); + + let cmd_arg0: u32 = (w_len as u32) << 16 | (r_len as u32) << 8 | addr as u32; + + let r = share::scope::< + ( + AllowRw<_, DRIVER_NUM, { rw_allow::MASTER_RX }>, + AllowRo<_, DRIVER_NUM, { ro_allow::MASTER_TX }>, + Subscribe<_, DRIVER_NUM, { subscribe::MASTER_WRITE_READ }>, + ), + _, + _, + >(|handle| { + let (allow_rw, allow_ro, subscribe) = handle.split(); + S::allow_rw::(allow_rw, r_buf)?; + S::allow_ro::(allow_ro, w_buf)?; + S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::MASTER_WRITE_READ }>( + subscribe, &called, + )?; + // When this fails, `called` is guaranteed unmodified, + // because upcalls are never processed until we call `yield`. + S::command( + DRIVER_NUM, + i2c_master_slave_cmd::MASTER_WRITE_READ, + cmd_arg0, + 0, + ) + .to_result()?; + + loop { + S::yield_wait(); + if let Some((r0, _read_len, status)) = called.get() { + // TODO: The kernel I2C api does not currently return the read_len, so this + // will be invalid. We should keep track, likely assume the transfer was + // done if no error. See: tock@capsules/core/src/i2c_master_slave_driver.rs:129 + // see: https://github.com/tock/tock/issues/3735 + assert_eq!(r0, i2c_master_slave_cmd::MASTER_WRITE_READ); + return match status { + 0 => Ok(()), + e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), + }; + } + } + }); + // If the operation failed, make bytes received zero so that the caller isn't confused in case + // the error is not handled properly. That is, in case of an error, we cannot guarantee the + // number of bytes received. + if r.is_err() { + bytes_received = 0; + } + (bytes_received, r) + } + + /// # Summary + /// + /// Set the slave address for this device for slave mode operation. The IP should respond + /// to @addr. + /// + /// # Parameter + /// + /// * `addr`: Slave device address to set + /// + /// # Returns + /// On success: Returns Ok(()) + /// On failure: Err(ErrorCode) + pub fn i2c_master_slave_set_slave_address(addr: u8) -> Result<(), ErrorCode> { + // We do not count the R/W bit as part of the address, so the + // valid range is 0x00-0x7f + if addr > 0x7f { + return Err(ErrorCode::Invalid); + } + S::command( + DRIVER_NUM, + i2c_master_slave_cmd::SLAVE_SET_ADDR, + addr as u32, + 0, + ) + .to_result() + } + + /// # Summary + /// + /// Expect a write from master into the buffer pointed by @buf. This function is + /// synchronous and returns only when the operation has completed. + /// + /// TODO: Add async support + /// + /// Note: As we do not know the size of data to be sent from a master device, + /// it is suggested to allocated a large buffer to accommodate bigger transfers. + /// + /// # Parameter + /// + /// * `buf`: Buffer into which to copy data from master + /// + /// # Returns + /// On success: Returns (bytes_read, Ok(())) + /// On failure: (0, Err(ErrorCode)) + pub fn i2c_master_slave_write_recv_sync(buf: &mut [u8]) -> (usize, Result<(), ErrorCode>) { + let called: Cell> = Cell::new(None); + let mut bytes_recvd_ret: u32 = 0; + let r = share::scope::< + ( + AllowRw<_, DRIVER_NUM, { rw_allow::SLAVE_RX }>, + Subscribe<_, DRIVER_NUM, { subscribe::SLAVE_WRITE_RECV }>, + ), + _, + _, + >(|handle| { + let (allow_rw, subscribe) = handle.split(); + S::allow_rw::(allow_rw, buf)?; + S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::SLAVE_READ }>(subscribe, &called)?; + + S::command(DRIVER_NUM, i2c_master_slave_cmd::SLAVE_START_LISTEN, 0, 0).to_result()?; + + loop { + S::yield_wait(); + if let Some((r0, bytes_recvd, status)) = called.get() { + // TODO: Ensure we are returning from the correct upcall and not from an unexpected `read_expect` + // Everything in this module subscribes to `0`. Which can be problematic from an async context. + assert_eq!(r0, i2c_master_slave_cmd::SLAVE_START_LISTEN); + return match status { + 0 => { + bytes_recvd_ret = bytes_recvd; + Ok(()) + } + e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), + }; + } + } + }); + (bytes_recvd_ret as usize, r) + } + + /// # Summary + /// + /// Expect a write from master into the buffer pointed by @buf. This function is + /// synchronous and returns only when the operation has completed. + /// + /// TODO: Add async support + /// + /// # Parameter + /// + /// * `buf`: Buffer from which to transfer data from + /// * `len`: max number of bytes from buffer to transfer + /// + /// # Returns + /// On success: Returns (bytes_sent, Ok(())) + /// On failure: (0, Err(ErrorCode)) + pub fn i2c_master_slave_read_send_sync( + buf: &[u8], + len: usize, + ) -> (usize, Result<(), ErrorCode>) { + if len > buf.len() { + return (0, Err(ErrorCode::Invalid)); + } + let called: Cell> = Cell::new(None); + let mut bytes_sent_ret: u32 = 0; + let r = share::scope::< + ( + AllowRo<_, DRIVER_NUM, { ro_allow::SLAVE_TX }>, + Subscribe<_, DRIVER_NUM, { subscribe::SLAVE_READ_SEND }>, + ), + _, + _, + >(|handle| { + let (allow_ro, subscribe) = handle.split(); + S::allow_ro::(allow_ro, buf)?; + S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::SLAVE_READ }>(subscribe, &called)?; + + S::command( + DRIVER_NUM, + i2c_master_slave_cmd::SLAVE_READ_SEND, + len as u32, + 0, + ) + .to_result()?; + + loop { + S::yield_wait(); + if let Some((r0, bytes_sent, status)) = called.get() { + // TODO: Ensure we are returning from the correct upcall and not from an unexpected `read_expect` + // Everything in this module subscribes to `0`. Which can be problematic from an async context. + assert_eq!(r0, i2c_master_slave_cmd::SLAVE_READ_SEND); + return match status { + 0 => { + bytes_sent_ret = bytes_sent; + Ok(()) + } + e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), + }; + } + } + }); + (bytes_sent_ret as usize, r) + } +} + +/// System call configuration trait for `I2CMaster`. +pub trait Config: + platform::allow_ro::Config + platform::allow_rw::Config + platform::subscribe::Config +{ +} +impl + Config for T +{ +} + +// ----------------------------------------------------------------------------- +// Driver number and command IDs +// ----------------------------------------------------------------------------- +const DRIVER_NUM: u32 = 0x20006; + +#[allow(unused)] +mod subscribe { + // TODO: It seems like only 0 is supported by the i2c_master_slave capsule currently + // would be nice to improve this. + pub const MASTER_WRITE: u32 = 0; + pub const MASTER_WRITE_READ: u32 = 0; + pub const MASTER_READ: u32 = 0; + pub const SLAVE_READ: u32 = 0; + pub const SLAVE_WRITE_RECV: u32 = 0; + pub const SLAVE_READ_SEND: u32 = 0; +} + +/// Ids for read-only allow buffers +#[allow(unused)] +mod ro_allow { + pub const MASTER_TX: u32 = 0; + pub const SLAVE_TX: u32 = 2; + /// The number of allow buffers the kernel stores for this grant + pub const COUNT: u8 = 3; +} + +/// Ids for read-write allow buffers +#[allow(unused)] +mod rw_allow { + pub const MASTER_RX: u32 = 1; + pub const SLAVE_RX: u32 = 3; +} + +#[allow(unused)] +mod i2c_buffers { + pub const MASTER_WRITE: u32 = 0; + pub const MASTER_READ: u32 = 1; + pub const SLAVE_READ: u32 = 2; + pub const SLAVE_WRITE: u32 = 3; +} + +#[allow(unused)] +mod i2c_master_slave_cmd { + pub const MASTER_WRITE: u32 = 1; + pub const MASTER_READ: u32 = 2; + pub const SLAVE_START_LISTEN: u32 = 3; + pub const SLAVE_READ_SEND: u32 = 4; + pub const SLAVE_SET_ADDR: u32 = 6; + pub const MASTER_WRITE_READ: u32 = 7; +} diff --git a/src/lib.rs b/src/lib.rs index 5d6f32e0..2da42507 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,10 @@ pub mod gpio { PullDown, PullNone, PullUp, }; } +pub mod i2c_master_slave { + use libtock_i2c_master_slave as i2c_master_slave; + pub type I2CMasterSlave = i2c_master_slave::I2CMasterSlave; +} pub mod leds { use libtock_leds as leds; pub type Leds = leds::Leds; From a67d6b1939cd1b3ab6ef735564bf31ca4af65e63 Mon Sep 17 00:00:00 2001 From: Wilfred Mallawa Date: Wed, 22 Nov 2023 13:09:21 +1000 Subject: [PATCH 2/2] examples: i2c: add master/slave examples Adds a master/slave sample that can be used to communicate with eachother. These two samples were verified using 2x Particle Borons (nrf52840 based). With one sample running on one board, the other on the 2nd board. Signed-off-by: Wilfred Mallawa --- examples/i2c_master_write_read.rs | 93 +++++++++++++++++++++++++++++++ examples/i2c_slave_send_recv.rs | 84 ++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 examples/i2c_master_write_read.rs create mode 100644 examples/i2c_slave_send_recv.rs diff --git a/examples/i2c_master_write_read.rs b/examples/i2c_master_write_read.rs new file mode 100644 index 00000000..08035cc2 --- /dev/null +++ b/examples/i2c_master_write_read.rs @@ -0,0 +1,93 @@ +//! This sample demonstrates setting up the i2c ip (assuming board has support) +//! for master mode. In the event loop, we write some bytes to the target, then +//! attempt to read some bytes from the target. +//! +//! This sample is tested with `i2c_slave_send_recv.rs` sample running on the +//! slave device. That sample uses the synchronous slave api, so the order of operations +//! is important to ensure we don't cause the slave to stretch clocks if it hasn't setup +//! send buffers in time. + +#![no_main] +#![no_std] +use core::fmt::Write; +use libtock::alarm::{Alarm, Milliseconds}; +use libtock::console::Console; +use libtock::i2c_master_slave::I2CMasterSlave; +use libtock::runtime::{set_main, stack_size}; + +set_main! {main} +stack_size! {0x400} + +pub const SLAVE_DEVICE_ADDR: u16 = 0x69; + +fn main() { + let addr = SLAVE_DEVICE_ADDR; + // 7-bit addressing + assert!(addr <= 0x7f); + let mut tx_buf: [u8; 4] = [0; 4]; + // Write 4 bytes to the slave + let tx_len = 4; + let mut rx_buf: [u8; 2] = [0; 2]; + // Attempt to read 2 bytes from the slave + let rx_len = 2; + + writeln!(Console::writer(), "i2c-master: write-read sample\r").unwrap(); + writeln!( + Console::writer(), + "i2c-master: slave address 0x{:x}!\r", + addr + ) + .unwrap(); + + let mut i: u32 = 0; + loop { + writeln!( + Console::writer(), + "i2c-master: write-read operation {:?}\r", + i + ) + .unwrap(); + + // Change up the data in tx-buffer + tx_buf[0] = tx_buf[0].wrapping_add(2); + tx_buf[1] = tx_buf[1].wrapping_add(4); + tx_buf[2] = tx_buf[2].wrapping_add(6); + tx_buf[3] = tx_buf[3].wrapping_add(8); + + if let Err(why) = I2CMasterSlave::i2c_master_slave_write_sync(addr, &tx_buf, tx_len) { + writeln!( + Console::writer(), + "i2c-master: write operation failed {:?}", + why + ) + .unwrap(); + } else { + // This sample target the i2c_slave_send_recv.rs sample, which is synchronous. + // so allow some time for it to setup 'send' buffer. + Alarm::sleep_for(Milliseconds(200)).unwrap(); + + let r = I2CMasterSlave::i2c_master_slave_read_sync(addr, &mut rx_buf, rx_len); + match r.1 { + Ok(()) => { + writeln!( + Console::writer(), + "{:} bytes read from slave | data received (0h): {:x?}\r\n", + r.0, + rx_buf + ) + .unwrap(); + } + Err(why) => { + writeln!( + Console::writer(), + "i2c-master: read operation failed {:?}", + why + ) + .unwrap(); + } + } + i += 1; + } + Alarm::sleep_for(Milliseconds(1000)).unwrap(); + } +} diff --git a/examples/i2c_slave_send_recv.rs b/examples/i2c_slave_send_recv.rs new file mode 100644 index 00000000..f0a1ec82 --- /dev/null +++ b/examples/i2c_slave_send_recv.rs @@ -0,0 +1,84 @@ +//! This sample demonstrates setting up the i2c ip (assuming board has support) +//! for target mode. In the event loop, we first expect the master to write some data +//! then we setup a response packet. +//! +//! NOTE: The device (based on hwip) may stretch clocks by holding the SCL line low if the master attempts to +//! read data before we have setup the read data buffers. +//! +//! This sample is tested with `i2c_master_write_read.rs` sample running on the +//! master device. + +#![no_main] +#![no_std] +use core::fmt::Write; +use libtock::console::Console; +use libtock::i2c_master_slave::I2CMasterSlave; +use libtock::runtime::{set_main, stack_size}; + +set_main! {main} +stack_size! {0x400} +pub const SLAVE_DEVICE_ADDR: u8 = 0x69; +fn main() { + let mut rx_buf: [u8; 8] = [0; 8]; + let mut tx_buf: [u8; 8] = [0; 8]; + let addr: u8 = SLAVE_DEVICE_ADDR; + // 7-bit addressing + assert!(addr <= 0x7f); + + writeln!(Console::writer(), "i2c-slave: setting up\r").unwrap(); + writeln!(Console::writer(), "i2c-slave: address 0x{:x}!\r", addr).unwrap(); + + I2CMasterSlave::i2c_master_slave_set_slave_address(addr).expect("i2c-target: Failed to listen"); + let mut i: u32 = 0; + loop { + writeln!(Console::writer(), "i2c-slave: operation {:?}\r", i).unwrap(); + + // Expect a write, if the master reads here, the IP may stretch clocks! + let r = I2CMasterSlave::i2c_master_slave_write_recv_sync(&mut rx_buf); + + if let Err(why) = r.1 { + writeln!( + Console::writer(), + "i2c-slave: error to receiving data {:?}\r", + why + ) + .unwrap(); + } else { + writeln!( + Console::writer(), + "{:} bytes received from master | buf: {:x?}\r", + r.0, + rx_buf + ) + .unwrap(); + + // Note: The master should allow a little delay when communicating with this slave + // as we are doing everything synchronously. + // Expect a 2 byte read by master and let's keep changing the values + tx_buf[0] = tx_buf[0].wrapping_add(1); + tx_buf[1] = tx_buf[1].wrapping_add(5); + let r = I2CMasterSlave::i2c_master_slave_read_send_sync(&tx_buf, tx_buf.len()); + + match r.1 { + Ok(()) => { + writeln!( + Console::writer(), + "{:} bytes read by master | data sent: {:x?}\r", + r.0, + tx_buf + ) + .unwrap(); + i += 1; + } + Err(why) => { + writeln!( + Console::writer(), + "i2c-slave: error setting up read_send {:?}\r", + why + ) + .unwrap(); + } + } + } + } +}