Skip to content

Commit

Permalink
Merge pull request #440 from nrf-rs/i2c
Browse files Browse the repository at this point in the history
Implement embedded-hal 1.0 I2c trait for Twi and Twim
  • Loading branch information
qwandor authored Apr 16, 2024
2 parents 9e0aa2d + b6cc800 commit ead3042
Show file tree
Hide file tree
Showing 2 changed files with 307 additions and 2 deletions.
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.
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)? };

// 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

0 comments on commit ead3042

Please sign in to comment.