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

Implement embedded-hal 1.0 I2c trait for Twi and Twim #440

Merged
merged 7 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
97 changes: 95 additions & 2 deletions nrf-hal-common/src/twi.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
//! HAL interface to the TWI peripheral.

use core::ops::Deref;

use crate::{
gpio::{Floating, Input, Pin},
pac::{twi0, GPIO, TWI0, TWI1},
};
use core::ops::Deref;
use embedded_hal::i2c::{self, ErrorKind, ErrorType, I2c, Operation};

pub use twi0::frequency::FREQUENCY_A as Frequency;

Expand Down Expand Up @@ -250,6 +250,92 @@ where
}
}

impl<T> ErrorType for Twi<T> {
type Error = Error;
}

impl<T: Instance> I2c for Twi<T> {
fn transaction(
&mut self,
address: u8,
operations: &mut [Operation],
) -> Result<(), Self::Error> {
// Make sure all previously used shortcuts are disabled.
self.0
.shorts
.write(|w| w.bb_stop().disabled().bb_suspend().disabled());

// Set Slave I2C address.
self.0
.address
.write(|w| unsafe { w.address().bits(address.into()) });

// `Some(true)` if the last operation was a read, `Some(false)` if it was a write.
qwandor marked this conversation as resolved.
Show resolved Hide resolved
let mut last_operation_read = None;
let operations_count = operations.len();
for (i, operation) in operations.into_iter().enumerate() {
match operation {
Operation::Read(buffer) => {
// Clear reception event.
self.0.events_rxdready.write(|w| unsafe { w.bits(0) });

if last_operation_read != Some(true) {
// Start data reception.
self.0.tasks_startrx.write(|w| unsafe { w.bits(1) });
}

if let Some((last, before)) = buffer.split_last_mut() {
for byte in before {
*byte = self.recv_byte()?;
}

if i == operations_count - 1 {
// Send stop after receiving the last byte.
self.0.events_stopped.write(|w| unsafe { w.bits(0) });
self.0.tasks_stop.write(|w| unsafe { w.bits(1) });

*last = self.recv_byte()?;

// Wait until stop was sent.
while self.0.events_stopped.read().bits() == 0 {
// Bail out if we get an error instead.
if self.0.events_error.read().bits() != 0 {
self.0.events_error.write(|w| unsafe { w.bits(0) });
return Err(Error::Transmit);
}
}
} else {
*last = self.recv_byte()?;
}
} else {
self.send_stop()?;
}
last_operation_read = Some(true);
}
Operation::Write(buffer) => {
if last_operation_read != Some(false) {
// Start data transmission.
self.0.tasks_starttx.write(|w| unsafe { w.bits(1) });
}

// Clock out all bytes.
for byte in *buffer {
self.send_byte(*byte)?;
}

if i == operations_count - 1 {
// Send stop.
self.send_stop()?;
}
last_operation_read = Some(false);
}
}
}

Ok(())
}
}

#[cfg(feature = "embedded-hal-02")]
impl<T> embedded_hal_02::blocking::i2c::Write for Twi<T>
where
Expand Down Expand Up @@ -308,6 +394,13 @@ pub enum Error {
Receive,
}

impl i2c::Error for Error {
fn kind(&self) -> ErrorKind {
// TODO
ErrorKind::Other
}
}

/// Implemented by all TWIM instances.
pub trait Instance: Deref<Target = twi0::RegisterBlock> + sealed::Sealed {}

Expand Down
212 changes: 212 additions & 0 deletions nrf-hal-common/src/twim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//! - nRF52840: Section 6.31
use core::ops::Deref;
use core::sync::atomic::{compiler_fence, Ordering::SeqCst};
use embedded_hal::i2c::{self, ErrorKind, ErrorType, I2c, NoAcknowledgeSource, Operation};

#[cfg(any(feature = "9160", feature = "5340-app", feature = "5340-net"))]
use crate::pac::{twim0_ns as twim0, TWIM0_NS as TWIM0};
Expand Down Expand Up @@ -194,6 +195,10 @@ where
self.0.events_stopped.reset();
break;
}
if self.0.events_suspended.read().bits() != 0 {
self.0.events_suspended.reset();
break;
}
if self.0.events_error.read().bits() != 0 {
self.0.events_error.reset();
self.0.tasks_stop.write(|w| unsafe { w.bits(1) });
Expand Down Expand Up @@ -392,6 +397,196 @@ where
},
)
}

fn write_part(&mut self, buffer: &[u8], final_operation: bool) -> Result<(), Error> {
compiler_fence(SeqCst);
unsafe { self.set_tx_buffer(buffer)? };
Copy link

Choose a reason for hiding this comment

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

No fence required here?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not entirely sure. Added some more fences to be on the safe side.


// Set appropriate lasttx shortcut.
if final_operation {
self.0.shorts.write(|w| w.lasttx_stop().enabled());
} else {
self.0.shorts.write(|w| w.lasttx_suspend().enabled());
}

// Start write.
self.0.tasks_starttx.write(|w| unsafe { w.bits(1) });
self.0.tasks_resume.write(|w| unsafe { w.bits(1) });

self.wait();
compiler_fence(SeqCst);
self.read_errorsrc()?;
if self.0.txd.amount.read().bits() != buffer.len() as u32 {
return Err(Error::Transmit);
}

Ok(())
}

fn read_part(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
compiler_fence(SeqCst);
unsafe { self.set_rx_buffer(buffer)? };

// TODO: We should suspend rather than stopping if there are more operations to
// follow, but for some reason that results in an overrun error and reading bad
// data in the next read.
self.0.shorts.write(|w| w.lastrx_stop().enabled());

// Start read.
self.0.tasks_startrx.write(|w| unsafe { w.bits(1) });
self.0.tasks_resume.write(|w| unsafe { w.bits(1) });

self.wait();
compiler_fence(SeqCst);
self.read_errorsrc()?;
if self.0.rxd.amount.read().bits() != buffer.len() as u32 {
return Err(Error::Receive);
}

Ok(())
}
}

impl<T> ErrorType for Twim<T> {
type Error = Error;
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum OperationType {
Read,
Write,
}

impl<T: Instance> I2c for Twim<T> {
fn transaction(
&mut self,
address: u8,
operations: &mut [Operation],
) -> Result<(), Self::Error> {
compiler_fence(SeqCst);

// Buffer used when writing data from flash, or combining multiple consecutive write operations.
let mut tx_copy = [0; FORCE_COPY_BUFFER_SIZE];
// Number of bytes waiting in `tx_copy` to be sent.
let mut pending_tx_bytes = 0;
// Buffer used when combining multiple consecutive read operations.
let mut rx_copy = [0; FORCE_COPY_BUFFER_SIZE];
// Number of bytes from earlier read operations not yet actually read.
let mut pending_rx_bytes = 0;

self.0
.address
.write(|w| unsafe { w.address().bits(address) });

for i in 0..operations.len() {
let next_operation_type = match operations.get(i + 1) {
None => None,
Some(Operation::Write(_)) => Some(OperationType::Write),
Some(Operation::Read(_)) => Some(OperationType::Read),
};
let operation = &mut operations[i];

// Clear events
self.0.events_stopped.reset();
self.0.events_error.reset();
self.0.events_lasttx.reset();
self.0.events_lastrx.reset();
self.clear_errorsrc();

match operation {
Operation::Read(buffer) => {
if buffer.len() > FORCE_COPY_BUFFER_SIZE - pending_rx_bytes {
// Splitting into multiple reads isn't going to work, so just return an
// error.
return Err(Error::RxBufferTooLong);
} else if pending_rx_bytes == 0
&& next_operation_type != Some(OperationType::Read)
{
// Simple case: there are no consecutive read operations, so receive
// directly.
self.read_part(buffer)?;
} else {
pending_rx_bytes += buffer.len();

// If the next operation is not a read (or these is no next operation),
// receive into `rx_copy` now.
if next_operation_type != Some(OperationType::Read) {
self.read_part(&mut rx_copy[..pending_rx_bytes])?;

// Copy the resulting data back to the various buffers.
for j in (0..=i).rev() {
if let Operation::Read(buffer) = &mut operations[j] {
buffer.copy_from_slice(
&rx_copy[pending_rx_bytes - buffer.len()..pending_rx_bytes],
);
pending_rx_bytes -= buffer.len();
} else {
break;
}
}

assert_eq!(pending_rx_bytes, 0);
}
}
}
Operation::Write(buffer) => {
// Will the current buffer fit in the remaining space in `tx_copy`? If not,
// send `tx_copy` immediately.
if buffer.len() > FORCE_COPY_BUFFER_SIZE - pending_tx_bytes
&& pending_tx_bytes > 0
{
self.write_part(&tx_copy[..pending_tx_bytes], false)?;
pending_tx_bytes = 0;
}

if crate::slice_in_ram(buffer)
&& pending_tx_bytes == 0
&& next_operation_type != Some(OperationType::Write)
{
// Simple case: the buffer is in RAM, and there are no consecutive write
// operations, so send it directly.
self.write_part(buffer, next_operation_type.is_none())?;
} else if buffer.len() > FORCE_COPY_BUFFER_SIZE {
// This must be true because if it wasn't we must have hit the case above to send
// `tx_copy` immediately and reset `pending_tx_bytes` to 0.
assert!(pending_tx_bytes == 0);

// Send the buffer in chunks immediately.
let num_chunks = buffer.len().div_ceil(FORCE_COPY_BUFFER_SIZE);
for (chunk_index, chunk) in buffer
.chunks(FORCE_COPY_BUFFER_SIZE)
.into_iter()
.enumerate()
{
tx_copy[..chunk.len()].copy_from_slice(chunk);
self.write_part(
&tx_copy[..chunk.len()],
next_operation_type.is_none() && chunk_index == num_chunks - 1,
)?;
}
} else {
// Copy the current buffer to `tx_copy`. It must fit, as otherwise we
// would have hit one of the cases above.
tx_copy[pending_tx_bytes..pending_tx_bytes + buffer.len()]
.copy_from_slice(&buffer);
pending_tx_bytes += buffer.len();

// If the next operation is not a write (or there is no next operation),
// send `tx_copy` now.
if next_operation_type != Some(OperationType::Write) {
self.write_part(
&tx_copy[..pending_tx_bytes],
next_operation_type.is_none(),
)?;
pending_tx_bytes = 0;
}
}
}
}
}

Ok(())
}
}

#[cfg(feature = "embedded-hal-02")]
Expand Down Expand Up @@ -473,6 +668,23 @@ pub enum Error {
Overrun,
}

impl i2c::Error for Error {
fn kind(&self) -> ErrorKind {
match self {
Self::TxBufferTooLong
| Self::RxBufferTooLong
| Self::TxBufferZeroLength
| Self::RxBufferZeroLength
| Self::Transmit
| Self::Receive
| Self::DMABufferNotInDataMemory => ErrorKind::Other,
Self::AddressNack => ErrorKind::NoAcknowledge(NoAcknowledgeSource::Address),
Self::DataNack => ErrorKind::NoAcknowledge(NoAcknowledgeSource::Data),
Self::Overrun => ErrorKind::Overrun,
}
}
}

/// Implemented by all TWIM instances
pub trait Instance: Deref<Target = twim0::RegisterBlock> + sealed::Sealed {}

Expand Down
Loading