Skip to content

Commit 7b58f2b

Browse files
Merge pull request #165 from kalkyl/twim_copy
Twim: Implicitly copy buffer into RAM if needed when using embedded hal traits
2 parents 8f13227 + edc316d commit 7b58f2b

File tree

3 files changed

+141
-7
lines changed

3 files changed

+141
-7
lines changed

nrf-hal-common/,

Whitespace-only changes.

nrf-hal-common/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ pub mod target_constants {
7474
pub const SRAM_LOWER: usize = 0x2000_0000;
7575
pub const SRAM_UPPER: usize = 0x3000_0000;
7676
pub const FORCE_COPY_BUFFER_SIZE: usize = 255;
77+
const _CHECK_FORCE_COPY_BUFFER_SIZE: usize = EASY_DMA_SIZE - FORCE_COPY_BUFFER_SIZE;
78+
// ERROR: FORCE_COPY_BUFFER_SIZE must be <= EASY_DMA_SIZE
7779
}
7880
#[cfg(any(feature = "52840", feature = "52833", feature = "9160"))]
7981
pub mod target_constants {
@@ -83,6 +85,8 @@ pub mod target_constants {
8385
pub const SRAM_LOWER: usize = 0x2000_0000;
8486
pub const SRAM_UPPER: usize = 0x3000_0000;
8587
pub const FORCE_COPY_BUFFER_SIZE: usize = 1024;
88+
const _CHECK_FORCE_COPY_BUFFER_SIZE: usize = EASY_DMA_SIZE - FORCE_COPY_BUFFER_SIZE;
89+
// ERROR: FORCE_COPY_BUFFER_SIZE must be <= EASY_DMA_SIZE
8690
}
8791

8892
/// Does this slice reside entirely within RAM?

nrf-hal-common/src/twim.rs

Lines changed: 137 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ use crate::target::TWIM1;
1818

1919
use crate::{
2020
gpio::{Floating, Input, Pin},
21-
slice_in_ram_or,
22-
target_constants::EASY_DMA_SIZE,
21+
slice_in_ram, slice_in_ram_or,
22+
target_constants::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE},
2323
};
2424

2525
pub use twim0::frequency::FREQUENCY_A as Frequency;
@@ -133,7 +133,7 @@ where
133133
while self.0.events_lasttx.read().bits() == 0 {}
134134
self.0.events_lasttx.write(|w| w); // reset event
135135

136-
// Stop read operation
136+
// Stop write operation
137137
self.0.tasks_stop.write(|w|
138138
// `1` is a valid value to write to task registers.
139139
unsafe { w.bits(1) });
@@ -229,7 +229,8 @@ where
229229
/// Write data to an I2C slave, then read data from the slave without
230230
/// triggering a stop condition between the two
231231
///
232-
/// The buffer must have a length of at most 255 bytes.
232+
/// The buffers must have a length of at most 255 bytes on the nRF52832
233+
/// and at most 65535 bytes on the nRF52840.
233234
pub fn write_then_read(
234235
&mut self,
235236
address: u8,
@@ -333,6 +334,122 @@ where
333334
Ok(())
334335
}
335336

337+
/// Copy data into RAM and write to an I2C slave, then read data from the slave without
338+
/// triggering a stop condition between the two
339+
///
340+
/// The read buffer must have a length of at most 255 bytes on the nRF52832
341+
/// and at most 65535 bytes on the nRF52840.
342+
pub fn copy_write_then_read(
343+
&mut self,
344+
address: u8,
345+
tx_buffer: &[u8],
346+
rx_buffer: &mut [u8],
347+
) -> Result<(), Error> {
348+
if rx_buffer.len() > EASY_DMA_SIZE {
349+
return Err(Error::RxBufferTooLong);
350+
}
351+
352+
// Conservative compiler fence to prevent optimizations that do not
353+
// take in to account actions by DMA. The fence has been placed here,
354+
// before any DMA action has started
355+
compiler_fence(SeqCst);
356+
357+
self.0
358+
.address
359+
.write(|w| unsafe { w.address().bits(address) });
360+
361+
// Set up the DMA read
362+
self.0.rxd.ptr.write(|w|
363+
// We're giving the register a pointer to the stack. Since we're
364+
// waiting for the I2C transaction to end before this stack pointer
365+
// becomes invalid, there's nothing wrong here.
366+
//
367+
// The PTR field is a full 32 bits wide and accepts the full range
368+
// of values.
369+
unsafe { w.ptr().bits(rx_buffer.as_mut_ptr() as u32) });
370+
self.0.rxd.maxcnt.write(|w|
371+
// We're giving it the length of the buffer, so no danger of
372+
// accessing invalid memory. We have verified that the length of the
373+
// buffer fits in an `u8`, so the cast to the type of maxcnt
374+
// is also fine.
375+
//
376+
// Note that that nrf52840 maxcnt is a wider
377+
// type than a u8, so we use a `_` cast rather than a `u8` cast.
378+
// The MAXCNT field is thus at least 8 bits wide and accepts the
379+
// full range of values that fit in a `u8`.
380+
unsafe { w.maxcnt().bits(rx_buffer.len() as _) });
381+
382+
// Chunk write data
383+
let wr_buffer = &mut [0; FORCE_COPY_BUFFER_SIZE][..];
384+
for chunk in tx_buffer.chunks(FORCE_COPY_BUFFER_SIZE) {
385+
// Copy chunk into RAM
386+
wr_buffer[..chunk.len()].copy_from_slice(chunk);
387+
388+
// Set up the DMA write
389+
self.0.txd.ptr.write(|w|
390+
// We're giving the register a pointer to the stack. Since we're
391+
// waiting for the I2C transaction to end before this stack pointer
392+
// becomes invalid, there's nothing wrong here.
393+
//
394+
// The PTR field is a full 32 bits wide and accepts the full range
395+
// of values.
396+
unsafe { w.ptr().bits(wr_buffer.as_ptr() as u32) });
397+
398+
self.0.txd.maxcnt.write(|w|
399+
// We're giving it the length of the buffer, so no danger of
400+
// accessing invalid memory. We have verified that the length of the
401+
// buffer fits in an `u8`, so the cast to `u8` is also fine.
402+
//
403+
// The MAXCNT field is 8 bits wide and accepts the full range of
404+
// values.
405+
unsafe { w.maxcnt().bits(wr_buffer.len() as _) });
406+
407+
// Start write operation
408+
self.0.tasks_starttx.write(|w|
409+
// `1` is a valid value to write to task registers.
410+
unsafe { w.bits(1) });
411+
412+
// Wait until write operation is about to end
413+
while self.0.events_lasttx.read().bits() == 0 {}
414+
self.0.events_lasttx.write(|w| w); // reset event
415+
416+
// Check for bad writes
417+
if self.0.txd.amount.read().bits() != wr_buffer.len() as u32 {
418+
return Err(Error::Transmit);
419+
}
420+
}
421+
422+
// Start read operation
423+
self.0.tasks_startrx.write(|w|
424+
// `1` is a valid value to write to task registers.
425+
unsafe { w.bits(1) });
426+
427+
// Wait until read operation is about to end
428+
while self.0.events_lastrx.read().bits() == 0 {}
429+
self.0.events_lastrx.write(|w| w); // reset event
430+
431+
// Stop read operation
432+
self.0.tasks_stop.write(|w|
433+
// `1` is a valid value to write to task registers.
434+
unsafe { w.bits(1) });
435+
436+
// Wait until total operation has ended
437+
while self.0.events_stopped.read().bits() == 0 {}
438+
self.0.events_stopped.write(|w| w); // reset event
439+
440+
// Conservative compiler fence to prevent optimizations that do not
441+
// take in to account actions by DMA. The fence has been placed here,
442+
// after all possible DMA actions have completed
443+
compiler_fence(SeqCst);
444+
445+
// Check for bad reads
446+
if self.0.rxd.amount.read().bits() != rx_buffer.len() as u32 {
447+
return Err(Error::Receive);
448+
}
449+
450+
Ok(())
451+
}
452+
336453
/// Return the raw interface to the underlying TWIM peripheral
337454
pub fn free(self) -> T {
338455
self.0
@@ -348,7 +465,16 @@ where
348465
type Error = Error;
349466

350467
fn write<'w>(&mut self, addr: u8, bytes: &'w [u8]) -> Result<(), Error> {
351-
self.write(addr, bytes)
468+
if slice_in_ram(bytes) {
469+
self.write(addr, bytes)
470+
} else {
471+
let buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..];
472+
for chunk in bytes.chunks(FORCE_COPY_BUFFER_SIZE) {
473+
buf[..chunk.len()].copy_from_slice(chunk);
474+
self.write(addr, &buf[..chunk.len()])?;
475+
}
476+
Ok(())
477+
}
352478
}
353479
}
354480

@@ -375,11 +501,15 @@ where
375501
bytes: &'w [u8],
376502
buffer: &'w mut [u8],
377503
) -> Result<(), Error> {
378-
self.write_then_read(addr, bytes, buffer)
504+
if slice_in_ram(bytes) {
505+
self.write_then_read(addr, bytes, buffer)
506+
} else {
507+
self.copy_write_then_read(addr, bytes, buffer)
508+
}
379509
}
380510
}
381511

382-
/// The pins used by the TWIN peripheral
512+
/// The pins used by the TWIM peripheral
383513
///
384514
/// Currently, only P0 pins are supported.
385515
pub struct Pins {

0 commit comments

Comments
 (0)