atsamd_hal/sercom/i2c.rs
1//! Use the SERCOM peripheral for I2C communications
2//!
3//! Configuring an I2C peripheral occurs in three steps. First, you must create
4//! a set of [`Pads`] for use by the peripheral. Next, you assemble pieces into
5//! a [`Config`] struct. After configuring the peripheral, you then [`enable`]
6//! it, yielding a functional [`I2c`] struct. Transactions are performed using
7//! traits from embedded-hal ([v1.0](crate::ehal) and blocking traits from
8//! [v0.2](`crate::ehal_02`)).
9//!
10//! # [`Pads`]
11//!
12//! A [`Sercom`] uses two [`Pin`]s as peripheral [`Pad`]s, but only certain
13//! [`Pin`] combinations are acceptable. In particular, all [`Pin`]s must be
14//! mapped to the same [`Sercom`], and SDA is always [`Pad0`], while SCL is
15//! always [`Pad1`] (see the datasheet). This HAL makes it impossible to use
16//! invalid [`Pin`]/[`Pad`] combinations, and the [`Pads`] struct is responsible
17//! for enforcing these constraints.
18//!
19//!
20//! A [`Pads`] type takes three or four type parameters, depending on the chip.
21//! The first type always specifies the [`Sercom`]. On SAMx5x chips, the second
22//! type specifies the `IoSet`. The remaining two, `SDA` and `SCL` represent the
23//! SDA and SCL pads respectively. A [`Pad`] is just a [`Pin`] configured in the
24//! correct [`PinMode`] that implements [`IsPad`]. The
25//! [`bsp_pins!`](crate::bsp_pins) macro can be used to define convenient type
26//! aliases for [`Pad`] types.
27//!
28//! ```no_run
29//! use atsamd_hal::gpio::{PA08, PA09, AlternateC};
30//! use atsamd_hal::sercom::{Sercom0, i2c};
31//! use atsamd_hal::typelevel::NoneT;
32//!
33//! // SAMx5x-specific imports
34//! use atsamd_hal::sercom::pad::IoSet1;
35//!
36//! type Sda = Pin<PA08, AlternateC>;
37//! type Scl = Pin<PA09, AlternateC>;
38//!
39//! // SAMD11/SAMD21 version
40//! type Pads = i2c::Pads<Sercom0, Sda, Scl>;
41//! // SAMx5x version
42//! type Pads = i2c::Pads<Sercom0, IoSet1, Sda, Scl>;
43//! ```
44//!
45//! Alternatively, you can use the [`PadsFromIds`] alias to define a set of
46//! `Pads` in terms of [`PinId`]s instead of [`Pin`]s. This is useful when you
47//! don't have [`Pin`] aliases pre-defined.
48//!
49//! ```no_run
50//! use atsamd_hal::gpio::{PA08, PA09};
51//! use atsamd_hal::sercom::{Sercom0, i2c};
52//!
53//! type Pads = i2c::PadsFromIds<Sercom0, PA08, PA09>;
54//! ```
55//!
56//! Instances of [`Pads`] are created using the [`new`](Pads::new) method.
57//!
58//! On SAMD21 and SAMx5x chips, [`new`](Pads::new) method automatically convert
59//! each pin to the correct [`PinMode`]. But for SAMD11 chips, users must
60//! manually convert each pin before calling the builder methods. This is a
61//! consequence of inherent ambiguities in the SAMD11 SERCOM pad definitions.
62//! Specifically, the same [`PinId`] can correspond to two different [`PadNum`]s
63//! for the *same* `Sercom`.
64//!
65//! ```no_run
66//! use atsamd_hal::pac::Peripherals;
67//! use atsamd_hal::gpio::Pins;
68//! use atsamd_hal::sercom::{Sercom0, i2c};
69//!
70//! let mut peripherals = Peripherals::take().unwrap();
71//! let pins = Pins::new(peripherals.PORT);
72//! let pads = i2c::Pads::<Sercom0>::new(pins.pa08, pins.pa09);
73//! ```
74//!
75//! # [`Config`]
76//!
77//! Next, create a [`Config`] struct, which represents the I2C peripheral in its
78//! disabled state. A [`Config`] is specified with one type parameters, the
79//! [`Pads`] type.
80//!
81//! Upon creation, the [`Config`] takes ownership of both the [`Pads`] struct
82//! and the PAC [`Sercom`] struct. It takes a reference to the PM, so that it
83//! can enable the APB clock, and it takes a frequency to indicate the GCLK
84//! configuration. Users are responsible for correctly configuring the GCLK.
85//!
86//! ```no_run
87//! use atsamd_hal::gpio::{PA08, PA09};
88//! use atsamd_hal::sercom::{Sercom0, i2c};
89//!
90//! type Pads = i2c::PadsFromIds<Sercom0, PA08, PA09>;
91//! type Config = i2c::Config<Pads>;
92//!
93//! let pm = peripherals.PM;
94//! let sercom = peripherals.SERCOM0;
95//! // Configure GCLK for 10 MHz
96//! let freq = 10.mhz();
97//! let config = i2c::Config::new(&pm, sercom, pads, freq);
98//! ```
99//!
100//! The [`Config`] struct can configure the peripheral in one of two ways:
101//!
102//! * A set of methods is provided to use in a builder pattern: for example
103//! [`baud`](Config::baud), [`run_in_standby`](Config::run_in_standby), etc.
104//! These methods take `self` and return `Self`.
105//! * A set of methods is provided to use as setters: for example
106//! [`set_baud`](Config::set_baud),
107//! [`set_run_in_standby`](Config::set_run_in_standby), etc. These methods
108//! take `&mut self` and return nothing.
109//!
110//! In any case, the peripheral setup ends with a call to [`enable`], which
111//! consumes the [`Config`] and returns an enabled [`I2c`] peripheral.
112//!
113//! ```no_run
114//! let i2c = i2c::Config::new(&pm, sercom, pads, freq)
115//! .baud(1.mhz())
116//! .enable();
117//! ```
118//!
119//! Alternatively,
120//!
121//! ```no_run
122//! let i2c = i2c::Config::new(&mclk, sercom, pads, freq);
123//! i2c.set_baud(1.mhz());
124//! let i2c = i2c.enable();
125//! ```
126//!
127//! ## Reading the current configuration
128//!
129//! It is possible to read the current configuration by using the getter methods
130//! provided: for example [`get_baud`](Config::get_baud),
131//! [`get_run_in_standby`](Config::get_run_in_standby), etc.
132//!
133//! # [`I2c`]
134//!
135//! [`I2c`] structs can only be created from a [`Config`]. They have one type
136//! parameter, representing the underlying [`Config`].
137//!
138//! Only the [`I2c`] struct can actually perform transactions. To do so, use the
139//! [`embedded_hal::i2c::I2c`] trait.
140//!
141//! ```
142//! use embedded_hal::i2c::I2c;
143//!
144//! i2c.write(0x54, 0x0fe).unwrap();
145//! ```
146//!
147//! # Reading the current configuration
148//!
149//! The `AsRef<Config<P>>` trait is implemented for `I2c<Config<P>>`. This means
150//! you can use the `get_` methods implemented for `Config`, since they take an
151//! `&self` argument.
152//!
153//! ```no_run
154//! // Assume i2c is a I2c<C<P>>
155//! let baud = i2c.as_ref().get_baud();
156//! ```
157//!
158//! # Reconfiguring
159//!
160//! The [`reconfigure`] method gives out an `&mut Config` reference, which can
161//! then use the `set_*` methods.
162//!
163//! ```no_run
164//! use atsamd_hal::sercom::i2c::I2c;
165//!
166//! // Assume config is a valid Duplex I2C Config struct
167//! let i2c = config.enable();
168//!
169//! // Send/receive data...
170//!
171//! // Reconfigure I2C peripheral
172//! i2c.reconfigure(|c| c.set_run_in_standby(false));
173//!
174//! // Disable I2C peripheral
175//! let config = i2c.disable();
176//! ```
177//!
178//! # Non-supported features
179//!
180//! * Slave mode is not supported at this time.
181//! * High-speed mode is not supported.
182//! * 4-wire mode is not supported.
183//! * 32-bit extension mode is not supported (SAMx5x). If you need to transfer
184//! slices, consider using the DMA methods instead <span class="stab
185//! portability" title="Available on crate feature `dma`
186//! only"><code>dma</code></span>.
187//!
188//! # Using I2C with DMA <span class="stab portability" title="Available on crate feature `dma` only"><code>dma</code></span>
189//!
190//! This HAL includes support for DMA-enabled I2C transfers. Use
191//! [`I2c::with_dma_channel`] to attach a DMA channel to the [`I2c`] struct. A
192//! DMA-enabled [`I2c`] implements the blocking
193//! [`embedded_hal::i2c::I2c`](crate::ehal::i2c::I2c) trait, which can be used
194//! to perform I2C transfers which are fast, continuous and low jitter, even if
195//! they are preemped by a higher priority interrupt.
196//!
197//!
198//! ```no_run
199//! use atsamd_hal::dmac::channel::{AnyChannel, Ready};
200//! use atsand_hal::sercom::i2c::{I2c, AnyConfig, Error};
201//! use atsamd_hal::embedded_hal::i2c::I2c;
202//! fn i2c_write_with_dma<A: AnyConfig, C: AnyChannel<Status = Ready>>(i2c: I2c<A>, channel: C, bytes: &[u8]) -> Result<(), Error>{
203//! // Attach a DMA channel
204//! let i2c = i2c.with_dma_channel(channel);
205//! i2c.write(0x54, bytes)?;
206//! }
207//! ```
208//!
209//! ## Limitations of using DMA with I2C
210//!
211//! * The I2C peripheral only supports continuous DMA read/writes of up to 255
212//! bytes. Trying to read/write with a transfer of 256 bytes or more will
213//! result in a panic. This also applies to using [`I2c::transaction`] with
214//! adjacent write/read operations of the same type; the total number of bytes
215//! across all adjacent operations must not exceed 256. If you need continuous
216//! transfers of 256 bytes or more, use the non-DMA [`I2c`] implementations.
217//!
218//! * When using [`I2c::transaction`] or [`I2c::write_read`], the
219//! [`embedded_hal::i2c::I2c`] specification mandates that a REPEATED START
220//! (instead of a STOP+START) is sent between transactions of a different type
221//! (read/write). Unfortunately, in DMA mode, the hardware is only capable of
222//! sending STOP+START. If you absolutely need repeated starts, the only
223//! workaround is to use the I2C without DMA.
224//!
225//! * Using [`I2c::transaction`] consumes significantly more memory than the
226//! other methods provided by [`embedded_hal::i2c::I2c`] (at least 256 bytes
227//! extra).
228//!
229//! * When using [`I2c::transaction`], up to 17 adjacent operations of the same
230//! type can be continuously handled by DMA without CPU intervention. If you
231//! need more than 17 adjacent operations of the same type, the transfer will
232//! reverted to using the byte-by-byte (non-DMA) implementation.
233//!
234//! All these limitations also apply to I2C transfers in async mode when using
235//! DMA. They do not apply to I2C transfers in async mode when not using DMA.
236//!
237//! # `async` operation <span class="stab portability" title="Available on crate feature `async` only"><code>async</code></span>
238//!
239//! An [`I2c`] can be used for `async` operations. Configuring an [`I2c`] in
240//! async mode is relatively simple:
241//!
242//! * Bind the corresponding `SERCOM` interrupt source to the SPI
243//! [`InterruptHandler`] (refer to the module-level [`async_hal`]
244//! documentation for more information).
245//! * Turn a previously configured [`I2c`] into an [`I2cFuture`] by calling
246//! [`I2c::into_future`]
247//! * Optionally, add a DMA channel by using [`I2cFuture::with_dma_channel`].
248//! The API is exactly the same whether a DMA channel is used or not.
249//! * Use the provided async methods for reading or writing to the I2C
250//! peripheral. [`I2cFuture`] implements [`embedded_hal_async::i2c::I2c`].
251//!
252//! `I2cFuture` implements `AsRef<I2c>` and `AsMut<I2c>` so that it can be
253//! reconfigured using the regular [`I2c`] methods.
254//!
255//! ## Considerations when using `async` [`I2c`] with DMA <span class="stab portability" title="Available on crate feature `async` only"><code>async</code></span> <span class="stab portability" title="Available on crate feature `dma` only"><code>dma</code></span>
256//!
257//! * An [`I2c`] struct must be turned into an [`I2cFuture`] by calling
258//! [`I2c::into_future`] before calling `with_dma_channel`. The DMA channel
259//! itself must also be configured in async mode by using
260//! [`DmaController::into_future`](crate::dmac::DmaController::into_future).
261//! If a DMA channel is added to the [`I2c`] struct before it is turned into
262//! an [`I2cFuture`], it will not be able to use DMA in async mode.
263//!
264//! ```
265//! // This will work
266//! let i2c = i2c.into_future().with_dma_channel(channel);
267//!
268//! // This won't
269//! let i2c = i2c.with_dma_channel(channel).into_future();
270//! ```
271//!
272//! ### Safety considerations
273//!
274//! In `async` mode, an I2C+DMA transfer does not require `'static` source and
275//! destination buffers. This, in theory, makes its use `unsafe`. However it is
276//! marked as safe for better ergonomics, and to enable the implementation of
277//! the [`embedded_hal_async::i2c::I2c`] trait.
278//!
279//! This means that, as an user, you **must** ensure that the [`Future`]s
280//! returned by the [`embedded_hal_async::i2c::I2c`] methods may never be
281//! forgotten through [`forget`] or by wrapping them with a [`ManuallyDrop`].
282//!
283//! The returned futures implement [`Drop`] and will automatically stop any
284//! ongoing transfers; this guarantees that the memory occupied by the
285//! now-dropped buffers may not be corrupted by running transfers.
286//!
287//! This means that using functions like [`futures::select_biased`] to implement
288//! timeouts is safe; transfers will be safely cancelled if the timeout expires.
289//!
290//! This also means that should you [`forget`] this [`Future`] after its first
291//! [`poll`] call, the transfer will keep running, ruining the now-reclaimed
292//! memory, as well as the rest of your day.
293//!
294//! * `await`ing is fine: the [`Future`] will run to completion.
295//! * Dropping an incomplete transfer is also fine. Dropping can happen, for
296//! example, if the transfer doesn't complete before a timeout expires.
297//! * Dropping an incomplete transfer *without running its destructor* is
298//! **unsound** and will trigger undefined behavior.
299//!
300//! ```ignore
301//! async fn always_ready() {}
302//!
303//! let mut buffer = [0x00; 10];
304//!
305//! // This is completely safe
306//! i2c.read(&mut buffer).await?;
307//!
308//! // This is also safe: we launch a transfer, which is then immediately cancelled
309//! futures::select_biased! {
310//! _ = i2c.read(&mut buffer)?,
311//! _ = always_ready(),
312//! }
313//!
314//! // This, while contrived, is also safe.
315//! {
316//! use core::future::Future;
317//!
318//! let future = i2c.read(&mut buffer);
319//! futures::pin_mut!(future);
320//! // Assume ctx is a `core::task::Context` given out by the executor.
321//! // The future is polled, therefore starting the transfer
322//! future.as_mut().poll(ctx);
323//!
324//! // Future is dropped here - transfer is cancelled.
325//! }
326//!
327//! // DANGER: This is an example of undefined behavior
328//! {
329//! use core::future::Future;
330//! use core::ops::DerefMut;
331//!
332//! let future = core::mem::ManuallyDrop::new(i2c.read(&mut buffer));
333//! futures::pin_mut!(future);
334//! // To actually make this example compile, we would need to wrap the returned
335//! // future from `i2c.read()` in a newtype that implements Future, because we
336//! // can't actually call as_mut() without being able to name the type we want
337//! // to deref to.
338//! let future_ref: &mut SomeNewTypeFuture = &mut future.as_mut();
339//! future.as_mut().poll(ctx);
340//!
341//! // Future is NOT dropped here - transfer is not cancelled, resulting un UB.
342//! }
343//! ```
344//!
345//! As you can see, unsoundness is relatively hard to come by - however, caution
346//! should still be exercised.
347//!
348//! [`enable`]: Config::enable
349//! [`disable`]: I2c::disable
350//! [`reconfigure`]: I2c::reconfigure
351//! [`bsp_pins`]: crate::bsp_pins
352//! [`Sercom`]: crate::sercom::Sercom
353//! [`Pad0`]: crate::sercom::pad::Pad0
354//! [`Pad1`]: crate::sercom::pad::Pad1
355//! [`Pad`]: crate::sercom::pad::Pad
356//! [`IsPad`]: crate::sercom::pad::IsPad
357//! [`PadNum`]: crate::sercom::pad::PadNum
358//! [`Pin`]: crate::gpio::pin::Pin
359//! [`PinId`]: crate::gpio::pin::PinId
360//! [`PinMode`]: crate::gpio::pin::PinMode
361//! [`embedded_hal::i2c::I2c`]: crate::ehal::i2c::I2c
362//! [`I2c::transaction`]: crate::ehal::i2c::I2c::transaction
363//! [`I2c::write_read`]: crate::ehal::i2c::I2c::write_read
364//! [`async_hal`]: crate::async_hal
365//! [`forget`]: core::mem::forget
366//! [`ManuallyDrop`]: core::mem::ManuallyDrop
367//! [`Future`]: core::future::Future
368//! [`poll`]: core::future::Future::poll
369
370use atsamd_hal_macros::hal_module;
371
372#[hal_module(
373 any("sercom0-d11", "sercom0-d21") => "i2c/pads_thumbv6m.rs",
374 "sercom0-d5x" => "i2c/pads_thumbv7em.rs",
375)]
376mod pads {}
377
378pub use pads::*;
379
380mod reg;
381use reg::Registers;
382
383mod flags;
384pub use flags::*;
385
386mod config;
387pub use config::*;
388
389mod impl_ehal;
390
391#[cfg(feature = "async")]
392mod async_api;
393
394#[cfg(feature = "async")]
395pub use async_api::*;
396
397/// Word size for an I2C message
398pub type Word = u8;
399
400/// Inactive timeout configuration
401#[repr(u8)]
402#[derive(Clone, Copy)]
403pub enum InactiveTimeout {
404 /// Disabled
405 Disabled = 0x0,
406 /// 5-6 SCL cycles (50-60 us @ 100 kHz)
407 Us55 = 0x1,
408 ///10-11 SCL cycles (100-110 us @ 100 kHz)
409 Us105 = 0x2,
410 /// 20-21 SCL cycles (200-210 us @ 100 kHz)
411 Us205 = 0x3,
412}
413
414/// Abstraction over a I2C peripheral, allowing to perform I2C transactions.
415pub struct I2c<C: AnyConfig, D = crate::typelevel::NoneT> {
416 config: C,
417 _dma_channel: D,
418}
419
420impl<C: AnyConfig, D> I2c<C, D> {
421 /// Obtain a pointer to the `DATA` register. Necessary for DMA transfers.
422 #[inline]
423 pub fn data_ptr(&self) -> *mut Word {
424 self.config.as_ref().registers.data_ptr()
425 }
426
427 /// Read the interrupt flags
428 #[inline]
429 pub fn read_flags(&self) -> Flags {
430 self.config.as_ref().registers.read_flags()
431 }
432
433 /// Clear interrupt status flags
434 #[inline]
435 pub fn clear_flags(&mut self, flags: Flags) {
436 self.config.as_mut().registers.clear_flags(flags);
437 }
438
439 /// Enable interrupts for the specified flags.
440 #[inline]
441 pub fn enable_interrupts(&mut self, flags: Flags) {
442 self.config.as_mut().registers.enable_interrupts(flags);
443 }
444
445 /// Disable interrupts for the specified flags.
446 #[inline]
447 pub fn disable_interrupts(&mut self, flags: Flags) {
448 self.config.as_mut().registers.disable_interrupts(flags);
449 }
450
451 /// Read the status flags
452 #[inline]
453 pub fn read_status(&self) -> Status {
454 self.config.as_ref().registers.read_status()
455 }
456
457 /// Clear the status flags
458 #[inline]
459 pub fn clear_status(&mut self, status: Status) {
460 self.config.as_mut().registers.clear_status(status);
461 }
462
463 #[cfg(feature = "dma")]
464 #[inline]
465 pub(super) fn start_dma_write(&mut self, address: u8, xfer_len: u8) {
466 self.config
467 .as_mut()
468 .registers
469 .start_dma_write(address, xfer_len)
470 }
471
472 #[cfg(feature = "dma")]
473 #[inline]
474 pub(super) fn start_dma_read(&mut self, address: u8, xfer_len: u8) {
475 self.config
476 .as_mut()
477 .registers
478 .start_dma_read(address, xfer_len)
479 }
480
481 #[cfg(feature = "dma")]
482 #[inline]
483 pub(super) fn check_bus_status(&self) -> Result<(), Error> {
484 self.config.as_ref().registers.check_bus_status()
485 }
486
487 #[inline]
488 fn do_write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> {
489 self.config.as_mut().registers.do_write(addr, bytes)
490 }
491
492 /// Continue a write operation that was issued before with
493 /// [`do_write`](Self::do_write) or [`continue_write`](Self::continue_write)
494 /// without a repeated start condition in between
495 #[inline]
496 fn continue_write(&mut self, bytes: &[u8]) -> Result<(), Error> {
497 self.config.as_mut().registers.continue_write(bytes)
498 }
499
500 #[inline]
501 fn do_read(&mut self, addr: u8, bytes: &mut [u8]) -> Result<(), Error> {
502 self.config.as_mut().registers.do_read(addr, bytes)
503 }
504
505 /// Continue a read operation that was issued before with
506 /// [`do_read`](Self::do_read) or [`continue_read`](Self::continue_read)
507 /// without a repeated start condition in between
508 #[inline]
509 fn continue_read(&mut self, bytes: &mut [u8]) -> Result<(), Error> {
510 self.config.as_mut().registers.continue_read(bytes)
511 }
512
513 #[inline]
514 fn do_write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> {
515 self.config
516 .as_mut()
517 .registers
518 .do_write_read(addr, bytes, buffer)
519 }
520 #[inline]
521 fn cmd_stop(&mut self) {
522 self.config.as_mut().registers.cmd_stop()
523 }
524
525 /// Reconfigure the I2C peripheral.
526 ///
527 /// Calling this method will temporarily disable the SERCOM peripheral, as
528 /// some registers are enable-protected. This may interrupt any ongoing
529 /// transactions.
530 ///
531 /// ```
532 /// use atsamd_hal::sercom::i2c::I2c;
533 /// i2c.reconfigure(|c| c.set_run_in_standby(false));
534 /// ```
535 #[inline]
536 pub fn reconfigure<F>(&mut self, update: F)
537 where
538 F: FnOnce(&mut SpecificConfig<C>),
539 {
540 self.config.as_mut().registers.enable_peripheral(false);
541 update(self.config.as_mut());
542 self.config.as_mut().registers.enable_peripheral(true);
543 }
544
545 /// Disable the I2C peripheral and return the underlying [`Config`]
546 #[inline]
547 pub fn disable(self) -> C {
548 let mut config = self.config;
549 config.as_mut().registers.disable();
550 config
551 }
552}
553
554impl<C: AnyConfig> I2c<C> {
555 /// Attach a DMA channel to this [`I2c`]. Its
556 /// [`embedded_hal::i2c::I2c`](crate::ehal::i2c::I2c) implementation will
557 /// use DMA to carry out its transactions.
558 #[cfg(feature = "dma")]
559 #[inline]
560 pub fn with_dma_channel<Chan: crate::dmac::AnyChannel<Status = crate::dmac::Ready>>(
561 self,
562 channel: Chan,
563 ) -> I2c<C, Chan> {
564 I2c {
565 config: self.config,
566 _dma_channel: channel,
567 }
568 }
569}
570
571#[cfg(feature = "dma")]
572impl<C, D, S> I2c<C, D>
573where
574 C: AnyConfig,
575 D: crate::dmac::AnyChannel<Status = S>,
576 S: crate::dmac::ReadyChannel,
577{
578 /// Reclaim the DMA channel. Any subsequent I2C operations will no longer
579 /// use DMA.
580 pub fn take_dma_channel(self) -> (I2c<C, crate::typelevel::NoneT>, D) {
581 (
582 I2c {
583 config: self.config,
584 _dma_channel: crate::typelevel::NoneT,
585 },
586 self._dma_channel,
587 )
588 }
589}
590
591impl<P: PadSet> AsRef<Config<P>> for I2c<Config<P>> {
592 #[inline]
593 fn as_ref(&self) -> &Config<P> {
594 self.config.as_ref()
595 }
596}