diff --git a/embassy-nrf/src/chips/nrf52810.rs b/embassy-nrf/src/chips/nrf52810.rs index 7fe35b27f..9f3ed9cf3 100644 --- a/embassy-nrf/src/chips/nrf52810.rs +++ b/embassy-nrf/src/chips/nrf52810.rs @@ -114,6 +114,8 @@ impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); impl_spim!(SPI0, SPIM0, SPIM0_SPIS0_SPI0); +impl_twim!(TWI0, TWIM0, TWIM0_TWIS0_TWI0); + impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER2, TIMER2, TIMER2); diff --git a/embassy-nrf/src/chips/nrf52811.rs b/embassy-nrf/src/chips/nrf52811.rs index e3f4f18ba..6728f6a41 100644 --- a/embassy-nrf/src/chips/nrf52811.rs +++ b/embassy-nrf/src/chips/nrf52811.rs @@ -115,6 +115,8 @@ impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); impl_spim!(TWISPI0, SPIM0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0); impl_spim!(SPI1, SPIM1, SPIM1_SPIS1_SPI1); +impl_twim!(TWISPI0, TWIM0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0); + impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER2, TIMER2, TIMER2); diff --git a/embassy-nrf/src/chips/nrf52820.rs b/embassy-nrf/src/chips/nrf52820.rs index e367a7916..2f9eb5f53 100644 --- a/embassy-nrf/src/chips/nrf52820.rs +++ b/embassy-nrf/src/chips/nrf52820.rs @@ -116,6 +116,9 @@ impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); impl_spim!(TWISPI0, SPIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); +impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); + impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER2, TIMER2, TIMER2); diff --git a/embassy-nrf/src/chips/nrf52832.rs b/embassy-nrf/src/chips/nrf52832.rs index e0669d93e..015bc51f8 100644 --- a/embassy-nrf/src/chips/nrf52832.rs +++ b/embassy-nrf/src/chips/nrf52832.rs @@ -121,6 +121,9 @@ impl_spim!(TWISPI0, SPIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); impl_spim!(SPI2, SPIM2, SPIM2_SPIS2_SPI2); +impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); +impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); + impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER2, TIMER2, TIMER2); diff --git a/embassy-nrf/src/chips/nrf52833.rs b/embassy-nrf/src/chips/nrf52833.rs index a0b434229..404883934 100644 --- a/embassy-nrf/src/chips/nrf52833.rs +++ b/embassy-nrf/src/chips/nrf52833.rs @@ -142,6 +142,9 @@ impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); impl_spim!(SPI2, SPIM2, SPIM2_SPIS2_SPI2); impl_spim!(SPI3, SPIM3, SPIM3); +impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); +impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); + impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER2, TIMER2, TIMER2); diff --git a/embassy-nrf/src/chips/nrf52840.rs b/embassy-nrf/src/chips/nrf52840.rs index 9a9f99201..26dfc7e8f 100644 --- a/embassy-nrf/src/chips/nrf52840.rs +++ b/embassy-nrf/src/chips/nrf52840.rs @@ -145,6 +145,9 @@ impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); impl_spim!(SPI2, SPIM2, SPIM2_SPIS2_SPI2); impl_spim!(SPI3, SPIM3, SPIM3); +impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); +impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); + impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER2, TIMER2, TIMER2); diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 066bf5f6f..2b35fdd6a 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -37,6 +37,7 @@ pub mod saadc; pub mod spim; pub mod system; pub mod timer; +pub mod twim; pub mod uarte; // This mod MUST go last, so that it sees all the `impl_foo!` macros diff --git a/embassy-nrf/src/twim.rs b/embassy-nrf/src/twim.rs new file mode 100644 index 000000000..cbaf97dff --- /dev/null +++ b/embassy-nrf/src/twim.rs @@ -0,0 +1,531 @@ +#![macro_use] + +//! HAL interface to the TWIM peripheral. +//! +//! See product specification: +//! +//! - nRF52832: Section 33 +//! - nRF52840: Section 6.31 +use core::marker::PhantomData; +use core::sync::atomic::{compiler_fence, Ordering::SeqCst}; +use embassy::interrupt::{Interrupt, InterruptExt}; +use embassy::util::{AtomicWaker, Unborrow}; +use embassy_extras::unborrow; + +use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE}; +use crate::fmt::*; +use crate::gpio::Pin as GpioPin; +use crate::pac; +use crate::util::{slice_in_ram, slice_in_ram_or}; + +pub enum Frequency { + #[doc = "26738688: 100 kbps"] + K100 = 26738688, + #[doc = "67108864: 250 kbps"] + K250 = 67108864, + #[doc = "104857600: 400 kbps"] + K400 = 104857600, +} + +#[non_exhaustive] +pub struct Config { + pub frequency: Frequency, +} + +impl Default for Config { + fn default() -> Self { + Self { + frequency: Frequency::K100, + } + } +} + +/// Interface to a TWIM instance. +pub struct Twim<'d, T: Instance> { + peri: T, + irq: T::Interrupt, + phantom: PhantomData<&'d mut T>, +} + +impl<'d, T: Instance> Twim<'d, T> { + pub fn new( + twim: impl Unborrow + 'd, + irq: impl Unborrow + 'd, + sda: impl Unborrow + 'd, + scl: impl Unborrow + 'd, + config: Config, + ) -> Self { + unborrow!(twim, irq, sda, scl); + + let r = T::regs(); + + // Configure pins + sda.conf().write(|w| { + w.dir().input(); + w.input().connect(); + w.pull().pullup(); + w.drive().s0d1(); + w + }); + scl.conf().write(|w| { + w.dir().input(); + w.input().connect(); + w.pull().pullup(); + w.drive().s0d1(); + w + }); + + // Select pins. + r.psel.sda.write(|w| unsafe { w.bits(sda.psel_bits()) }); + r.psel.scl.write(|w| unsafe { w.bits(scl.psel_bits()) }); + + // Enable TWIM instance. + r.enable.write(|w| w.enable().enabled()); + + // Configure frequency. + r.frequency + .write(|w| unsafe { w.frequency().bits(config.frequency as u32) }); + + // Disable all events interrupts + r.intenclr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); + + irq.set_handler(Self::on_interrupt); + irq.unpend(); + irq.enable(); + + Self { + peri: twim, + irq, + phantom: PhantomData, + } + } + + fn on_interrupt(_: *mut ()) { + let r = T::regs(); + let s = T::state(); + + if r.events_stopped.read().bits() != 0 { + s.end_waker.wake(); + r.intenclr.write(|w| w.stopped().clear()); + } + if r.events_error.read().bits() != 0 { + s.end_waker.wake(); + r.intenclr.write(|w| w.error().clear()); + } + } + + /// Set TX buffer, checking that it is in RAM and has suitable length. + unsafe fn set_tx_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> { + slice_in_ram_or(buffer, Error::DMABufferNotInDataMemory)?; + + if buffer.len() == 0 { + return Err(Error::TxBufferZeroLength); + } + if buffer.len() > EASY_DMA_SIZE { + return Err(Error::TxBufferTooLong); + } + + let r = T::regs(); + + r.txd.ptr.write(|w| + // We're giving the register a pointer to the stack. Since we're + // waiting for the I2C transaction to end before this stack pointer + // becomes invalid, there's nothing wrong here. + // + // The PTR field is a full 32 bits wide and accepts the full range + // of values. + w.ptr().bits(buffer.as_ptr() as u32)); + r.txd.maxcnt.write(|w| + // We're giving it the length of the buffer, so no danger of + // accessing invalid memory. We have verified that the length of the + // buffer fits in an `u8`, so the cast to `u8` is also fine. + // + // The MAXCNT field is 8 bits wide and accepts the full range of + // values. + w.maxcnt().bits(buffer.len() as _)); + + Ok(()) + } + + /// Set RX buffer, checking that it has suitable length. + unsafe fn set_rx_buffer(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + // NOTE: RAM slice check is not necessary, as a mutable + // slice can only be built from data located in RAM. + + if buffer.len() == 0 { + return Err(Error::RxBufferZeroLength); + } + if buffer.len() > EASY_DMA_SIZE { + return Err(Error::RxBufferTooLong); + } + + let r = T::regs(); + + r.rxd.ptr.write(|w| + // We're giving the register a pointer to the stack. Since we're + // waiting for the I2C transaction to end before this stack pointer + // becomes invalid, there's nothing wrong here. + // + // The PTR field is a full 32 bits wide and accepts the full range + // of values. + w.ptr().bits(buffer.as_mut_ptr() as u32)); + r.rxd.maxcnt.write(|w| + // We're giving it the length of the buffer, so no danger of + // accessing invalid memory. We have verified that the length of the + // buffer fits in an `u8`, so the cast to the type of maxcnt + // is also fine. + // + // Note that that nrf52840 maxcnt is a wider + // type than a u8, so we use a `_` cast rather than a `u8` cast. + // The MAXCNT field is thus at least 8 bits wide and accepts the + // full range of values that fit in a `u8`. + w.maxcnt().bits(buffer.len() as _)); + + Ok(()) + } + + fn clear_errorsrc(&mut self) { + let r = T::regs(); + r.errorsrc + .write(|w| w.anack().bit(true).dnack().bit(true).overrun().bit(true)); + } + + /// Get Error instance, if any occurred. + fn read_errorsrc(&self) -> Result<(), Error> { + let r = T::regs(); + + let err = r.errorsrc.read(); + if err.anack().is_received() { + return Err(Error::AddressNack); + } + if err.dnack().is_received() { + return Err(Error::DataNack); + } + if err.overrun().is_received() { + return Err(Error::DataNack); + } + Ok(()) + } + + /// Wait for stop or error + fn wait(&mut self) { + let r = T::regs(); + loop { + if r.events_stopped.read().bits() != 0 { + r.events_stopped.reset(); + break; + } + if r.events_error.read().bits() != 0 { + r.events_error.reset(); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + } + } + } + + /// Write to an I2C slave. + /// + /// The buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + pub fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { + let r = T::regs(); + + // Conservative compiler fence to prevent optimizations that do not + // take in to account actions by DMA. The fence has been placed here, + // before any DMA action has started. + compiler_fence(SeqCst); + + r.address.write(|w| unsafe { w.address().bits(address) }); + + // Set up the DMA write. + unsafe { self.set_tx_buffer(buffer)? }; + + // Clear events + r.events_stopped.reset(); + r.events_error.reset(); + r.events_lasttx.reset(); + self.clear_errorsrc(); + + // Start write operation. + r.shorts.write(|w| w.lasttx_stop().enabled()); + r.tasks_starttx.write(|w| + // `1` is a valid value to write to task registers. + unsafe { w.bits(1) }); + + self.wait(); + + // Conservative compiler fence to prevent optimizations that do not + // take in to account actions by DMA. The fence has been placed here, + // after all possible DMA actions have completed. + compiler_fence(SeqCst); + + self.read_errorsrc()?; + + if r.txd.amount.read().bits() != buffer.len() as u32 { + return Err(Error::Transmit); + } + + Ok(()) + } + + /// Read from an I2C slave. + /// + /// The buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + pub fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { + let r = T::regs(); + + // Conservative compiler fence to prevent optimizations that do not + // take in to account actions by DMA. The fence has been placed here, + // before any DMA action has started. + compiler_fence(SeqCst); + + r.address.write(|w| unsafe { w.address().bits(address) }); + + // Set up the DMA read. + unsafe { self.set_rx_buffer(buffer)? }; + + // Clear events + r.events_stopped.reset(); + r.events_error.reset(); + self.clear_errorsrc(); + + // Start read operation. + r.shorts.write(|w| w.lastrx_stop().enabled()); + r.tasks_startrx.write(|w| + // `1` is a valid value to write to task registers. + unsafe { w.bits(1) }); + + self.wait(); + + // Conservative compiler fence to prevent optimizations that do not + // take in to account actions by DMA. The fence has been placed here, + // after all possible DMA actions have completed. + compiler_fence(SeqCst); + + self.read_errorsrc()?; + + if r.rxd.amount.read().bits() != buffer.len() as u32 { + return Err(Error::Receive); + } + + Ok(()) + } + + /// Write data to an I2C slave, then read data from the slave without + /// triggering a stop condition between the two. + /// + /// The buffers must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + pub fn write_then_read( + &mut self, + address: u8, + wr_buffer: &[u8], + rd_buffer: &mut [u8], + ) -> Result<(), Error> { + let r = T::regs(); + + // Conservative compiler fence to prevent optimizations that do not + // take in to account actions by DMA. The fence has been placed here, + // before any DMA action has started. + compiler_fence(SeqCst); + + r.address.write(|w| unsafe { w.address().bits(address) }); + + // Set up DMA buffers. + unsafe { + self.set_tx_buffer(wr_buffer)?; + self.set_rx_buffer(rd_buffer)?; + } + + // Clear events + r.events_stopped.reset(); + r.events_error.reset(); + self.clear_errorsrc(); + + // Start write+read operation. + r.shorts.write(|w| { + w.lasttx_startrx().enabled(); + w.lastrx_stop().enabled(); + w + }); + // `1` is a valid value to write to task registers. + r.tasks_starttx.write(|w| unsafe { w.bits(1) }); + + self.wait(); + + // Conservative compiler fence to prevent optimizations that do not + // take in to account actions by DMA. The fence has been placed here, + // after all possible DMA actions have completed. + compiler_fence(SeqCst); + + self.read_errorsrc()?; + + let bad_write = r.txd.amount.read().bits() != wr_buffer.len() as u32; + let bad_read = r.rxd.amount.read().bits() != rd_buffer.len() as u32; + + if bad_write { + return Err(Error::Transmit); + } + + if bad_read { + return Err(Error::Receive); + } + + Ok(()) + } + + /// Copy data into RAM and write to an I2C slave. + /// + /// The write buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 1024 bytes on the nRF52840. + pub fn copy_write(&mut self, address: u8, wr_buffer: &[u8]) -> Result<(), Error> { + if wr_buffer.len() > FORCE_COPY_BUFFER_SIZE { + return Err(Error::TxBufferTooLong); + } + + // Copy to RAM + let wr_ram_buffer = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()]; + wr_ram_buffer.copy_from_slice(wr_buffer); + + self.write(address, wr_ram_buffer) + } + + /// Copy data into RAM and write to an I2C slave, then read data from the slave without + /// triggering a stop condition between the two. + /// + /// The write buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 1024 bytes on the nRF52840. + /// + /// The read buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + pub fn copy_write_then_read( + &mut self, + address: u8, + wr_buffer: &[u8], + rd_buffer: &mut [u8], + ) -> Result<(), Error> { + if wr_buffer.len() > FORCE_COPY_BUFFER_SIZE { + return Err(Error::TxBufferTooLong); + } + + // Copy to RAM + let wr_ram_buffer = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()]; + wr_ram_buffer.copy_from_slice(wr_buffer); + + self.write_then_read(address, wr_ram_buffer, rd_buffer) + } +} + +impl<'a, T: Instance> Drop for Twim<'a, T> { + fn drop(&mut self) { + info!("twim drop"); + + // TODO when implementing async here, check for abort + + // disable! + let r = T::regs(); + r.enable.write(|w| w.enable().disabled()); + + info!("uarte drop: done"); + + // TODO: disable pins + } +} + +impl<'a, T: Instance> embedded_hal::blocking::i2c::Write for Twim<'a, T> { + type Error = Error; + + fn write<'w>(&mut self, addr: u8, bytes: &'w [u8]) -> Result<(), Error> { + if slice_in_ram(bytes) { + self.write(addr, bytes) + } else { + let buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..]; + for chunk in bytes.chunks(FORCE_COPY_BUFFER_SIZE) { + buf[..chunk.len()].copy_from_slice(chunk); + self.write(addr, &buf[..chunk.len()])?; + } + Ok(()) + } + } +} + +impl<'a, T: Instance> embedded_hal::blocking::i2c::Read for Twim<'a, T> { + type Error = Error; + + fn read<'w>(&mut self, addr: u8, bytes: &'w mut [u8]) -> Result<(), Error> { + self.read(addr, bytes) + } +} + +impl<'a, T: Instance> embedded_hal::blocking::i2c::WriteRead for Twim<'a, T> { + type Error = Error; + + fn write_read<'w>( + &mut self, + addr: u8, + bytes: &'w [u8], + buffer: &'w mut [u8], + ) -> Result<(), Error> { + if slice_in_ram(bytes) { + self.write_then_read(addr, bytes, buffer) + } else { + self.copy_write_then_read(addr, bytes, buffer) + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum Error { + TxBufferTooLong, + RxBufferTooLong, + TxBufferZeroLength, + RxBufferZeroLength, + Transmit, + Receive, + DMABufferNotInDataMemory, + AddressNack, + DataNack, + Overrun, +} + +pub(crate) mod sealed { + use super::*; + + pub struct State { + pub end_waker: AtomicWaker, + } + + impl State { + pub const fn new() -> Self { + Self { + end_waker: AtomicWaker::new(), + } + } + } + + pub trait Instance { + fn regs() -> &'static pac::twim0::RegisterBlock; + fn state() -> &'static State; + } +} + +pub trait Instance: sealed::Instance + 'static { + type Interrupt: Interrupt; +} + +macro_rules! impl_twim { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::twim::sealed::Instance for peripherals::$type { + fn regs() -> &'static pac::twim0::RegisterBlock { + unsafe { &*pac::$pac_type::ptr() } + } + fn state() -> &'static crate::twim::sealed::State { + static STATE: crate::twim::sealed::State = crate::twim::sealed::State::new(); + &STATE + } + } + impl crate::twim::Instance for peripherals::$type { + type Interrupt = crate::interrupt::$irq; + } + }; +}